From d837624c0afdd3da47b731d7aba05016536684a5 Mon Sep 17 00:00:00 2001 From: Mrx <18278715334@163.com> Date: Thu, 19 Mar 2026 13:23:48 +0800 Subject: [PATCH] f --- .../certification_application_service.go | 6 +- .../certification_application_service_impl.go | 217 ++++++++++-------- .../dto/commands/certification_commands.go | 8 + .../dto/queries/certification_queries.go | 4 +- .../dto/responses/certification_responses.go | 10 +- .../enums/certification_status.go | 58 ++--- ...nterprise_info_submit_record_repository.go | 4 +- .../enterprise_info_submit_record_service.go | 12 +- ...nterprise_info_submit_record_repository.go | 30 +-- .../http/handlers/certification_handler.go | 30 ++- .../http/routes/certification_routes.go | 12 +- .../shared/middleware/comprehensive_logger.go | 2 +- 12 files changed, 222 insertions(+), 171 deletions(-) diff --git a/internal/application/certification/certification_application_service.go b/internal/application/certification/certification_application_service.go index 67fc0be..bb045b5 100644 --- a/internal/application/certification/certification_application_service.go +++ b/internal/application/certification/certification_application_service.go @@ -41,10 +41,12 @@ type CertificationApplicationService interface { AdminListSubmitRecords(ctx context.Context, query *queries.AdminListSubmitRecordsQuery) (*responses.AdminSubmitRecordsListResponse, error) // AdminGetSubmitRecordByID 管理端获取单条提交记录详情 AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error) - // AdminApproveSubmitRecord 管理端审核通过 + // AdminApproveSubmitRecord 管理端审核通过(按提交记录 ID) AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error - // AdminRejectSubmitRecord 管理端审核拒绝 + // AdminRejectSubmitRecord 管理端审核拒绝(按提交记录 ID) AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error + // AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准:info_submitted=通过 / info_rejected=拒绝) + AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error // ================ e签宝回调处理 ================ diff --git a/internal/application/certification/certification_application_service_impl.go b/internal/application/certification/certification_application_service_impl.go index b6f9d16..9cb67a9 100644 --- a/internal/application/certification/certification_application_service_impl.go +++ b/internal/application/certification/certification_application_service_impl.go @@ -109,10 +109,13 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( s.logger.Info("开始提交企业信息", zap.String("user_id", cmd.UserID)) - // 0. 若该用户已有待审核的提交记录,则不允许重复提交 + // 0. 若该用户已有待审核(认证状态仍在待审核),则不允许重复提交 latestRecord, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID) - if err == nil && latestRecord != nil && latestRecord.ManualReviewStatus == "pending" { - return nil, fmt.Errorf("您已有待审核的提交,请等待管理员审核后再操作") + if err == nil && latestRecord != nil { + cert, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID) + if loadErr == nil && cert != nil && cert.Status == enums.StatusInfoPendingReview { + return nil, fmt.Errorf("您已有待审核的提交,请等待管理员审核后再操作") + } } // 1.5 插入企业信息提交记录(包含扩展字段) @@ -259,16 +262,7 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return fmt.Errorf("加载认证信息失败: %s", err.Error()) } - // 已是「已提交企业信息」:说明已通过人工审核,直接返回当前认证数据,前端可刷新到企业认证步骤 - if cert.Status == enums.StatusInfoSubmitted { - response = s.convertToResponse(cert) - response.Metadata = map[string]interface{}{ - "next_action": "您已通过审核,请完成企业认证步骤", - } - return nil - } - - // 4. 提交企业信息进入人工审核(不调用 e签宝,不生成认证链接) + // 4. 提交企业信息进入人工审核(不在此处推断审核是否通过) err = cert.SubmitEnterpriseInfoForReview(enterpriseInfo) if err != nil { return fmt.Errorf("提交企业信息失败: %s", err.Error()) @@ -303,6 +297,25 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return response, nil } +// 审核状态检查(步骤二) +// 规则:企业信息提交成功后进入待审核;审核通过后才允许进行企业认证确认(ConfirmAuth)。 +func (s *CertificationApplicationServiceImpl) checkAuditStatus(ctx context.Context, cert *entities.Certification) error { + switch cert.Status { + case enums.StatusInfoSubmitted, + enums.StatusEnterpriseVerified, + enums.StatusContractApplied, + enums.StatusContractSigned, + enums.StatusCompleted: + return nil + case enums.StatusInfoPendingReview: + return fmt.Errorf("企业信息已提交,正在审核中") + case enums.StatusInfoRejected: + return fmt.Errorf("企业信息审核未通过") + default: + return fmt.Errorf("认证状态不正确,当前状态: %s", enums.GetStatusName(cert.Status)) + } +} + // ConfirmAuth 确认认证状态 func (s *CertificationApplicationServiceImpl) ConfirmAuth( ctx context.Context, @@ -314,9 +327,9 @@ func (s *CertificationApplicationServiceImpl) ConfirmAuth( return nil, fmt.Errorf("加载认证信息失败: %s", err.Error()) } - // 企业认证 - if cert.Status != enums.StatusInfoSubmitted { - return nil, fmt.Errorf("认证状态不正确,当前状态: %s", enums.GetStatusName(cert.Status)) + // 步骤二:审核状态检查(审核通过后才能进入企业认证确认) + if err := s.checkAuditStatus(ctx, cert); err != nil { + return nil, err } record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cert.UserID) if err != nil { @@ -730,12 +743,12 @@ func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords( query.Page = 1 } filter := repositories.ListSubmitRecordsFilter{ - Page: query.Page, - PageSize: query.PageSize, - ManualReviewStatus: query.ManualReviewStatus, - CompanyName: query.CompanyName, - LegalPersonPhone: query.LegalPersonPhone, - LegalPersonName: query.LegalPersonName, + Page: query.Page, + PageSize: query.PageSize, + CertificationStatus: query.CertificationStatus, + CompanyName: query.CompanyName, + LegalPersonPhone: query.LegalPersonPhone, + LegalPersonName: query.LegalPersonName, } result, err := s.enterpriseInfoSubmitRecordRepo.List(ctx, filter) if err != nil { @@ -755,8 +768,6 @@ func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords( LegalPersonName: r.LegalPersonName, SubmitAt: r.SubmitAt, Status: r.Status, - ManualReviewStatus: r.ManualReviewStatus, - ManualReviewedAt: r.ManualReviewedAt, CertificationStatus: certStatus, }) } @@ -805,10 +816,6 @@ func (s *CertificationApplicationServiceImpl) AdminGetSubmitRecordByID(ctx conte VerifiedAt: record.VerifiedAt, FailedAt: record.FailedAt, FailureReason: record.FailureReason, - ManualReviewStatus: record.ManualReviewStatus, - ManualReviewRemark: record.ManualReviewRemark, - ManualReviewedAt: record.ManualReviewedAt, - ManualReviewerID: record.ManualReviewerID, CertificationStatus: certStatus, CreatedAt: record.CreatedAt, UpdatedAt: record.UpdatedAt, @@ -821,17 +828,12 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte if err != nil { return fmt.Errorf("获取提交记录失败: %w", err) } - if record.ManualReviewStatus != "pending" { - return fmt.Errorf("该记录已审核,当前状态: %s", record.ManualReviewStatus) - } cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID) if err != nil { return fmt.Errorf("加载认证信息失败: %w", err) } - // 兼容线上脏数据:认证已进入「已提交企业信息」或更后续状态,但提交记录仍是 pending, - // 此时无需再走「待审核->通过」的状态机,只需要把提交记录补齐为 approved,避免管理端无法操作。 - // 说明:后续状态(如已企业认证/合同状态/完成)都意味着企业信息审核已经通过。 + // 幂等:认证已进入「已提交企业信息」或更后续状态,说明已通过审核,无需重复操作 switch cert.Status { case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, @@ -840,31 +842,11 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired: - record.MarkManualApproved(adminID, remark) - if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { - return fmt.Errorf("保存提交记录失败: %w", err) - } - s.logger.Info("已补齐提交记录人工审核为通过(认证已进入后续状态)", - zap.String("record_id", recordID), - zap.String("admin_id", adminID), - zap.String("user_id", record.UserID), - zap.String("certification_status", string(cert.Status)), - ) return nil } - // 兼容线上脏数据:提交记录已落库但当时事务失败导致认证仍为「待认证」,先同步为待审核再执行通过 if cert.Status != enums.StatusInfoPendingReview { - if err := s.syncCertToPendingReviewIfRecordPending(ctx, cert, record); err != nil { - return err - } - cert, err = s.aggregateService.LoadCertificationByUserID(ctx, record.UserID) - if err != nil { - return fmt.Errorf("加载认证信息失败: %w", err) - } - if cert.Status != enums.StatusInfoPendingReview { - return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) - } + return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) } enterpriseInfo := &certification_value_objects.EnterpriseInfo{ CompanyName: record.CompanyName, @@ -886,10 +868,6 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte if err != nil { return fmt.Errorf("生成企业认证链接失败: %w", err) } - record.MarkManualApproved(adminID, remark) - if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { - return fmt.Errorf("保存提交记录失败: %w", err) - } if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, adminID); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) } @@ -909,29 +887,24 @@ func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx contex if err != nil { return fmt.Errorf("获取提交记录失败: %w", err) } - if record.ManualReviewStatus != "pending" { - return fmt.Errorf("该记录已审核,当前状态: %s", record.ManualReviewStatus) - } cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID) if err != nil { return fmt.Errorf("加载认证信息失败: %w", err) } - // 兼容线上脏数据:提交记录已落库但当时事务失败导致认证仍为「待认证」,先同步为待审核再执行拒绝 - if cert.Status != enums.StatusInfoPendingReview { - if err := s.syncCertToPendingReviewIfRecordPending(ctx, cert, record); err != nil { - return err - } - cert, err = s.aggregateService.LoadCertificationByUserID(ctx, record.UserID) - if err != nil { - return fmt.Errorf("加载认证信息失败: %w", err) - } - if cert.Status != enums.StatusInfoPendingReview { - return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) - } + + // 幂等:认证已处于拒绝或后续状态,无需重复拒绝 + switch cert.Status { + case enums.StatusInfoRejected, + enums.StatusEnterpriseVerified, + enums.StatusContractApplied, + enums.StatusContractSigned, + enums.StatusCompleted, + enums.StatusContractRejected, + enums.StatusContractExpired: + return nil } - record.MarkManualRejected(adminID, remark) - if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { - return fmt.Errorf("保存提交记录失败: %w", err) + if cert.Status != enums.StatusInfoPendingReview { + return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) } if err := cert.RejectEnterpriseInfoReview(adminID, remark); err != nil { return fmt.Errorf("更新认证状态失败: %w", err) @@ -943,35 +916,75 @@ func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx contex return nil } -// ================ 辅助方法 ================ - -// syncCertToPendingReviewIfRecordPending 兼容历史脏数据:当认证为「待认证」或「已拒绝」且存在待审核提交记录时, -// 用该记录的企业信息把认证同步为「待审核」,便于管理员直接审核通过/拒绝。 -func (s *CertificationApplicationServiceImpl) syncCertToPendingReviewIfRecordPending(ctx context.Context, cert *entities.Certification, record *entities.EnterpriseInfoSubmitRecord) error { - if record.ManualReviewStatus != "pending" { +// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准) +func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus(ctx context.Context, cmd *commands.AdminTransitionCertificationStatusCommand) error { + cert, err := s.aggregateService.LoadCertificationByUserID(ctx, cmd.UserID) + if err != nil { + return fmt.Errorf("加载认证信息失败: %w", err) + } + record, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID) + if err != nil { + return fmt.Errorf("查找企业信息提交记录失败: %w", err) + } + if record == nil { + return fmt.Errorf("未找到该用户的企业信息提交记录") + } + switch cmd.TargetStatus { + case string(enums.StatusInfoSubmitted): + // 审核通过:与 AdminApproveSubmitRecord 一致,推状态并生成企业认证链接 + switch cert.Status { + case enums.StatusInfoSubmitted, enums.StatusEnterpriseVerified, enums.StatusContractApplied, + enums.StatusContractSigned, enums.StatusCompleted, enums.StatusContractRejected, enums.StatusContractExpired: + return nil + } + if cert.Status != enums.StatusInfoPendingReview { + return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) + } + enterpriseInfo := &certification_value_objects.EnterpriseInfo{ + CompanyName: record.CompanyName, UnifiedSocialCode: record.UnifiedSocialCode, + LegalPersonName: record.LegalPersonName, LegalPersonID: record.LegalPersonID, + LegalPersonPhone: record.LegalPersonPhone, EnterpriseAddress: record.EnterpriseAddress, + } + authURL, err := s.esignClient.GenerateEnterpriseAuth(&esign.EnterpriseAuthRequest{ + CompanyName: enterpriseInfo.CompanyName, UnifiedSocialCode: enterpriseInfo.UnifiedSocialCode, + LegalPersonName: enterpriseInfo.LegalPersonName, LegalPersonID: enterpriseInfo.LegalPersonID, + TransactorName: enterpriseInfo.LegalPersonName, TransactorMobile: enterpriseInfo.LegalPersonPhone, TransactorID: enterpriseInfo.LegalPersonID, + }) + if err != nil { + return fmt.Errorf("生成企业认证链接失败: %w", err) + } + if err := cert.ApproveEnterpriseInfoReview(authURL.AuthShortURL, authURL.AuthFlowID, cmd.AdminID); err != nil { + return fmt.Errorf("更新认证状态失败: %w", err) + } + if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { + return fmt.Errorf("保存认证信息失败: %w", err) + } + s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID)) return nil + case string(enums.StatusInfoRejected): + // 审核拒绝 + if cert.Status == enums.StatusInfoRejected || cert.Status == enums.StatusEnterpriseVerified || + cert.Status == enums.StatusContractApplied || cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted { + return nil + } + if cert.Status != enums.StatusInfoPendingReview { + return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) + } + if err := cert.RejectEnterpriseInfoReview(cmd.AdminID, cmd.Remark); err != nil { + return fmt.Errorf("更新认证状态失败: %w", err) + } + if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { + return fmt.Errorf("保存认证信息失败: %w", err) + } + s.logger.Info("管理端变更认证状态为拒绝", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID)) + return nil + default: + return fmt.Errorf("不支持的目标状态: %s", cmd.TargetStatus) } - if cert.Status != enums.StatusPending && cert.Status != enums.StatusInfoRejected { - return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) - } - enterpriseInfo := &certification_value_objects.EnterpriseInfo{ - CompanyName: record.CompanyName, - UnifiedSocialCode: record.UnifiedSocialCode, - LegalPersonName: record.LegalPersonName, - LegalPersonID: record.LegalPersonID, - LegalPersonPhone: record.LegalPersonPhone, - EnterpriseAddress: record.EnterpriseAddress, - } - if err := cert.SubmitEnterpriseInfoForReview(enterpriseInfo); err != nil { - return fmt.Errorf("同步认证为待审核失败: %w", err) - } - if err := s.aggregateService.SaveCertification(ctx, cert); err != nil { - return fmt.Errorf("保存认证信息失败: %w", err) - } - s.logger.Info("已同步认证为待审核(兼容历史脏数据)", zap.String("user_id", cert.UserID), zap.String("record_id", record.ID)) - return nil } +// ================ 辅助方法 ================ + // convertToResponse 转换实体为响应DTO func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.Certification) *responses.CertificationResponse { response := &responses.CertificationResponse{ diff --git a/internal/application/certification/dto/commands/certification_commands.go b/internal/application/certification/dto/commands/certification_commands.go index a1175cf..99fd29e 100644 --- a/internal/application/certification/dto/commands/certification_commands.go +++ b/internal/application/certification/dto/commands/certification_commands.go @@ -94,6 +94,14 @@ type ForceTransitionStatusCommand struct { Force bool `json:"force,omitempty"` // 是否强制执行,跳过业务规则验证 } +// AdminTransitionCertificationStatusCommand 管理端变更认证状态(以状态机为准,用于审核通过/拒绝等) +type AdminTransitionCertificationStatusCommand struct { + AdminID string `json:"-"` + UserID string `json:"user_id" validate:"required"` + TargetStatus string `json:"target_status" validate:"required,oneof=info_submitted info_rejected"` // 审核通过 -> info_submitted;审核拒绝 -> info_rejected + Remark string `json:"remark"` +} + // SubmitEnterpriseInfoCommand 提交企业信息命令 type SubmitEnterpriseInfoCommand struct { UserID string `json:"-" comment:"用户唯一标识,从JWT token获取,不在JSON中暴露"` diff --git a/internal/application/certification/dto/queries/certification_queries.go b/internal/application/certification/dto/queries/certification_queries.go index e741046..f6fa31b 100644 --- a/internal/application/certification/dto/queries/certification_queries.go +++ b/internal/application/certification/dto/queries/certification_queries.go @@ -193,11 +193,11 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool { return false } -// AdminListSubmitRecordsQuery 管理端企业信息提交记录列表查询 +// AdminListSubmitRecordsQuery 管理端企业信息提交记录列表查询(以状态机 certification_status 为准,不做审核状态筛选) type AdminListSubmitRecordsQuery struct { Page int `json:"page" form:"page"` PageSize int `json:"page_size" form:"page_size"` - ManualReviewStatus string `json:"manual_review_status" form:"manual_review_status"` // pending, approved, rejected,空为全部 + CertificationStatus string `json:"certification_status" form:"certification_status"` // 按认证状态筛选,如 info_pending_review / info_submitted / info_rejected,空为全部 CompanyName string `json:"company_name" form:"company_name"` // 企业名称(模糊搜索) LegalPersonPhone string `json:"legal_person_phone" form:"legal_person_phone"` // 法人手机号 LegalPersonName string `json:"legal_person_name" form:"legal_person_name"` // 法人姓名(模糊搜索) diff --git a/internal/application/certification/dto/responses/certification_responses.go b/internal/application/certification/dto/responses/certification_responses.go index 672d2b8..5327d0f 100644 --- a/internal/application/certification/dto/responses/certification_responses.go +++ b/internal/application/certification/dto/responses/certification_responses.go @@ -119,9 +119,7 @@ type AdminSubmitRecordItem struct { LegalPersonName string `json:"legal_person_name"` SubmitAt time.Time `json:"submit_at"` Status string `json:"status"` - ManualReviewStatus string `json:"manual_review_status"` - ManualReviewedAt *time.Time `json:"manual_reviewed_at,omitempty"` - CertificationStatus string `json:"certification_status,omitempty"` // 该用户当前认证状态,用于前端判断是否已完成企业认证并显示「已审核」 + CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准:info_pending_review/info_submitted/info_rejected 等 } // AdminSubmitRecordDetail 管理端提交记录详情(含完整信息与图片 URL) @@ -147,11 +145,7 @@ type AdminSubmitRecordDetail struct { VerifiedAt *time.Time `json:"verified_at,omitempty"` FailedAt *time.Time `json:"failed_at,omitempty"` FailureReason string `json:"failure_reason,omitempty"` - ManualReviewStatus string `json:"manual_review_status"` - ManualReviewRemark string `json:"manual_review_remark,omitempty"` - ManualReviewedAt *time.Time `json:"manual_reviewed_at,omitempty"` - ManualReviewerID string `json:"manual_reviewer_id,omitempty"` - CertificationStatus string `json:"certification_status,omitempty"` // 该用户当前认证状态,用于前端显示「已审核」 + CertificationStatus string `json:"certification_status,omitempty"` // 以状态机为准 CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } diff --git a/internal/domains/certification/enums/certification_status.go b/internal/domains/certification/enums/certification_status.go index 048b4c9..aaa7691 100644 --- a/internal/domains/certification/enums/certification_status.go +++ b/internal/domains/certification/enums/certification_status.go @@ -5,18 +5,18 @@ type CertificationStatus string const ( // === 主流程状态 === - StatusPending CertificationStatus = "pending" // 待认证 - StatusInfoPendingReview CertificationStatus = "info_pending_review" // 企业信息待人工审核 - StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息(审核通过) - StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证 - StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同 - StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同 - StatusCompleted CertificationStatus = "completed" // 认证完成 + StatusPending CertificationStatus = "pending" // 待认证 + StatusInfoPendingReview CertificationStatus = "info_pending_review" // 企业信息待人工审核 + StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息(审核通过) + StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证 + StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同 + StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同 + StatusCompleted CertificationStatus = "completed" // 认证完成 // === 失败状态 === StatusInfoRejected CertificationStatus = "info_rejected" // 企业信息被拒绝 StatusContractRejected CertificationStatus = "contract_rejected" // 合同被拒签 - StatusContractExpired CertificationStatus = "contract_expired" // 合同签署超时 + StatusContractExpired CertificationStatus = "contract_expired" // 合同签署超时 ) // AllStatuses 所有有效状态列表 @@ -145,15 +145,15 @@ func GetStatusPriority(status CertificationStatus) int { func GetProgressPercentage(status CertificationStatus) int { progressMap := map[CertificationStatus]int{ StatusPending: 0, - StatusInfoPendingReview: 15, - StatusInfoSubmitted: 25, - StatusEnterpriseVerified: 50, - StatusContractApplied: 75, - StatusContractSigned: 100, + StatusInfoPendingReview: 15, + StatusInfoSubmitted: 25, + StatusEnterpriseVerified: 50, + StatusContractApplied: 75, + StatusContractSigned: 100, StatusCompleted: 100, - StatusInfoRejected: 25, - StatusContractRejected: 75, - StatusContractExpired: 75, + StatusInfoRejected: 25, + StatusContractRejected: 75, + StatusContractExpired: 75, } if progress, exists := progressMap[status]; exists { @@ -166,7 +166,7 @@ func GetProgressPercentage(status CertificationStatus) int { func IsUserActionRequired(status CertificationStatus) bool { userActionRequired := map[CertificationStatus]bool{ StatusPending: true, // 需要提交企业信息 - StatusInfoPendingReview: false, // 等待人工审核 + StatusInfoPendingReview: false, // 等待人工审核 StatusInfoSubmitted: false, // 等待完成企业认证 StatusEnterpriseVerified: true, // 需要申请合同 StatusContractApplied: true, // 需要签署合同 @@ -277,18 +277,18 @@ func CanTransitionTo(currentStatus, targetStatus CertificationStatus) bool { // GetTransitionReason 获取状态转换的原因描述 func GetTransitionReason(from, to CertificationStatus) string { transitionReasons := map[string]string{ - string(StatusPending) + "->" + string(StatusInfoPendingReview): "用户提交企业信息,等待人工审核", - string(StatusInfoPendingReview) + "->" + string(StatusInfoSubmitted): "管理员审核通过", - string(StatusInfoPendingReview) + "->" + string(StatusInfoRejected): "管理员审核拒绝", - string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息", - string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功", - string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败", - string(StatusEnterpriseVerified) + "->" + string(StatusContractApplied): "用户申请签署合同", - string(StatusContractApplied) + "->" + string(StatusContractSigned): "e签宝合同签署成功", - string(StatusContractSigned) + "->" + string(StatusCompleted): "系统处理完成,认证成功", - string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同", - string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时", - string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息", + string(StatusPending) + "->" + string(StatusInfoPendingReview): "用户提交企业信息,等待人工审核", + string(StatusInfoPendingReview) + "->" + string(StatusInfoSubmitted): "管理员审核通过", + string(StatusInfoPendingReview) + "->" + string(StatusInfoRejected): "管理员审核拒绝", + string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息", + string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功", + string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败", + string(StatusEnterpriseVerified) + "->" + string(StatusContractApplied): "用户申请签署合同", + string(StatusContractApplied) + "->" + string(StatusContractSigned): "e签宝合同签署成功", + string(StatusContractSigned) + "->" + string(StatusCompleted): "系统处理完成,认证成功", + string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同", + string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时", + string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息", string(StatusContractRejected) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请", string(StatusContractExpired) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请", } diff --git a/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go b/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go index 4ffa430..97c06a1 100644 --- a/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go +++ b/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go @@ -5,9 +5,9 @@ import ( "tyapi-server/internal/domains/certification/entities" ) -// ListSubmitRecordsFilter 提交记录列表筛选 +// ListSubmitRecordsFilter 提交记录列表筛选(以状态机 certification 状态为准) type ListSubmitRecordsFilter struct { - ManualReviewStatus string // pending, approved, rejected,空表示全部 + CertificationStatus string // 认证状态筛选,如 info_pending_review / info_submitted / info_rejected,空为全部 CompanyName string // 企业名称(模糊搜索) LegalPersonPhone string // 法人手机号 LegalPersonName string // 法人姓名(模糊搜索) diff --git a/internal/domains/certification/services/enterprise_info_submit_record_service.go b/internal/domains/certification/services/enterprise_info_submit_record_service.go index 603d5cd..69a2d5f 100644 --- a/internal/domains/certification/services/enterprise_info_submit_record_service.go +++ b/internal/domains/certification/services/enterprise_info_submit_record_service.go @@ -82,12 +82,12 @@ func (s *EnterpriseInfoSubmitRecordService) ValidateWithWestdex(ctx context.Cont } // 开发环境下跳过外部验证 - // if s.appConfig.IsDevelopment() { - // s.logger.Info("开发环境:跳过企业信息外部验证", - // zap.String("company_name", info.CompanyName), - // zap.String("legal_person", info.LegalPersonName)) - // return nil - // } + if s.appConfig.IsDevelopment() { + s.logger.Info("开发环境:跳过企业信息外部验证", + zap.String("company_name", info.CompanyName), + zap.String("legal_person", info.LegalPersonName)) + return nil + } // 构建QYGL5CMP请求参数 reqDto := dto.QYGL5CMPReq{ diff --git a/internal/infrastructure/database/repositories/certification/gorm_enterprise_info_submit_record_repository.go b/internal/infrastructure/database/repositories/certification/gorm_enterprise_info_submit_record_repository.go index 9047e11..1156965 100644 --- a/internal/infrastructure/database/repositories/certification/gorm_enterprise_info_submit_record_repository.go +++ b/internal/infrastructure/database/repositories/certification/gorm_enterprise_info_submit_record_repository.go @@ -91,21 +91,22 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) ExistsByUnifiedSocialCodeExcl } func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, filter repositories.ListSubmitRecordsFilter) (*repositories.ListSubmitRecordsResult, error) { - db := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}) - if filter.ManualReviewStatus != "" { - db = db.Where("manual_review_status = ?", filter.ManualReviewStatus) + base := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}) + if filter.CertificationStatus != "" { + base = base.Joins("JOIN certifications ON certifications.user_id = enterprise_info_submit_records.user_id AND certifications.deleted_at IS NULL"). + Where("certifications.status = ?", filter.CertificationStatus) } if filter.CompanyName != "" { - db = db.Where("company_name LIKE ?", "%"+filter.CompanyName+"%") + base = base.Where("enterprise_info_submit_records.company_name LIKE ?", "%"+filter.CompanyName+"%") } if filter.LegalPersonPhone != "" { - db = db.Where("legal_person_phone = ?", filter.LegalPersonPhone) + base = base.Where("enterprise_info_submit_records.legal_person_phone = ?", filter.LegalPersonPhone) } if filter.LegalPersonName != "" { - db = db.Where("legal_person_name LIKE ?", "%"+filter.LegalPersonName+"%") + base = base.Where("enterprise_info_submit_records.legal_person_name LIKE ?", "%"+filter.LegalPersonName+"%") } var total int64 - if err := db.Count(&total).Error; err != nil { + if err := base.Count(&total).Error; err != nil { return nil, err } if filter.PageSize <= 0 { @@ -116,20 +117,21 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, fil } offset := (filter.Page - 1) * filter.PageSize var records []*entities.EnterpriseInfoSubmitRecord - q := r.GetDB(ctx) - if filter.ManualReviewStatus != "" { - q = q.Where("manual_review_status = ?", filter.ManualReviewStatus) + q := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}) + if filter.CertificationStatus != "" { + q = q.Joins("JOIN certifications ON certifications.user_id = enterprise_info_submit_records.user_id AND certifications.deleted_at IS NULL"). + Where("certifications.status = ?", filter.CertificationStatus) } if filter.CompanyName != "" { - q = q.Where("company_name LIKE ?", "%"+filter.CompanyName+"%") + q = q.Where("enterprise_info_submit_records.company_name LIKE ?", "%"+filter.CompanyName+"%") } if filter.LegalPersonPhone != "" { - q = q.Where("legal_person_phone = ?", filter.LegalPersonPhone) + q = q.Where("enterprise_info_submit_records.legal_person_phone = ?", filter.LegalPersonPhone) } if filter.LegalPersonName != "" { - q = q.Where("legal_person_name LIKE ?", "%"+filter.LegalPersonName+"%") + q = q.Where("enterprise_info_submit_records.legal_person_name LIKE ?", "%"+filter.LegalPersonName+"%") } - err := q.Order("submit_at DESC").Offset(offset).Limit(filter.PageSize).Find(&records).Error + err := q.Order("enterprise_info_submit_records.submit_at DESC").Offset(offset).Limit(filter.PageSize).Find(&records).Error if err != nil { return nil, err } diff --git a/internal/infrastructure/http/handlers/certification_handler.go b/internal/infrastructure/http/handlers/certification_handler.go index 0fa8089..be8c25d 100644 --- a/internal/infrastructure/http/handlers/certification_handler.go +++ b/internal/infrastructure/http/handlers/certification_handler.go @@ -458,7 +458,7 @@ func (h *CertificationHandler) AdminCompleteCertificationWithoutContract(c *gin. // @Security Bearer // @Param page query int false "页码" // @Param page_size query int false "每页条数" -// @Param manual_review_status query string false "审核状态 pending/approved/rejected" +// @Param certification_status query string false "按状态机筛选:info_pending_review/info_submitted/info_rejected,空为全部" // @Success 200 {object} responses.AdminSubmitRecordsListResponse // @Router /api/v1/certifications/admin/submit-records [get] func (h *CertificationHandler) AdminListSubmitRecords(c *gin.Context) { @@ -564,6 +564,34 @@ func (h *CertificationHandler) AdminRejectSubmitRecord(c *gin.Context) { h.response.Success(c, nil, "已拒绝") } +// AdminTransitionCertificationStatus 管理端按用户变更认证状态(以状态机为准) +// @Summary 管理端变更认证状态 +// @Tags 认证管理 +// @Accept json +// @Produce json +// @Security Bearer +// @Param request body commands.AdminTransitionCertificationStatusCommand true "user_id, target_status(info_submitted/info_rejected), remark" +// @Success 200 {object} map[string]interface{} +// @Router /api/v1/certifications/admin/transition-status [post] +func (h *CertificationHandler) AdminTransitionCertificationStatus(c *gin.Context) { + adminID := h.getCurrentUserID(c) + if adminID == "" { + h.response.Unauthorized(c, "未登录") + return + } + var cmd commands.AdminTransitionCertificationStatusCommand + if err := c.ShouldBindJSON(&cmd); err != nil { + h.response.BadRequest(c, "参数错误") + return + } + cmd.AdminID = adminID + if err := h.appService.AdminTransitionCertificationStatus(c.Request.Context(), &cmd); err != nil { + h.response.BadRequest(c, err.Error()) + return + } + h.response.Success(c, nil, "状态已更新") +} + // ================ 回调处理 ================ // HandleEsignCallback 处理e签宝回调 diff --git a/internal/infrastructure/http/routes/certification_routes.go b/internal/infrastructure/http/routes/certification_routes.go index d1ac1a1..338ff6d 100644 --- a/internal/infrastructure/http/routes/certification_routes.go +++ b/internal/infrastructure/http/routes/certification_routes.go @@ -77,10 +77,14 @@ func (r *CertificationRoutes) Register(router *http.GinRouter) { } - // 管理端企业审核(需管理员权限) - adminCertGroup := certificationGroup.Group("/admin/submit-records") - adminCertGroup.Use(r.auth.Handle()) - adminCertGroup.Use(r.admin.Handle()) + // 管理端企业审核(需管理员权限,以状态机状态为准) + adminGroup := certificationGroup.Group("/admin") + adminGroup.Use(r.auth.Handle()) + adminGroup.Use(r.admin.Handle()) + { + adminGroup.POST("/transition-status", r.handler.AdminTransitionCertificationStatus) + } + adminCertGroup := adminGroup.Group("/submit-records") { adminCertGroup.GET("", r.handler.AdminListSubmitRecords) adminCertGroup.GET("/:id", r.handler.AdminGetSubmitRecordByID) diff --git a/internal/shared/middleware/comprehensive_logger.go b/internal/shared/middleware/comprehensive_logger.go index 2668f3e..617f943 100644 --- a/internal/shared/middleware/comprehensive_logger.go +++ b/internal/shared/middleware/comprehensive_logger.go @@ -240,7 +240,7 @@ func (m *ComprehensiveLoggerMiddleware) logErrors(c *gin.Context, requestID, tra zap.String("user_id", userID), zap.String("method", c.Request.Method), zap.String("path", c.Request.URL.Path), - zap.String("error_type", string(ginErr.Type)), + zap.Uint64("error_type", uint64(ginErr.Type)), zap.Error(ginErr.Err), zap.Time("timestamp", time.Now()), }