add f
This commit is contained in:
@@ -1029,44 +1029,19 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte
|
|||||||
|
|
||||||
// AdminRejectSubmitRecord 管理端审核拒绝
|
// AdminRejectSubmitRecord 管理端审核拒绝
|
||||||
func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
||||||
if remark == "" {
|
|
||||||
return fmt.Errorf("拒绝时必须填写审核备注")
|
|
||||||
}
|
|
||||||
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("获取提交记录失败: %w", err)
|
return fmt.Errorf("获取提交记录失败: %w", err)
|
||||||
}
|
}
|
||||||
if record.Status != "verified" {
|
|
||||||
return fmt.Errorf("该条提交记录未通过前置校验或已失败,无法从后台拒绝(请查看历史失败原因)")
|
|
||||||
}
|
|
||||||
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID)
|
cert, err := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("加载认证信息失败: %w", err)
|
return fmt.Errorf("加载认证信息失败: %w", err)
|
||||||
}
|
}
|
||||||
|
if cert.UserID != record.UserID {
|
||||||
// 幂等:认证已处于拒绝或后续状态,无需重复拒绝
|
return fmt.Errorf("提交记录与认证用户不匹配,无法拒绝")
|
||||||
switch cert.Status {
|
|
||||||
case enums.StatusInfoRejected,
|
|
||||||
enums.StatusEnterpriseVerified,
|
|
||||||
enums.StatusContractApplied,
|
|
||||||
enums.StatusContractSigned,
|
|
||||||
enums.StatusCompleted,
|
|
||||||
enums.StatusContractRejected,
|
|
||||||
enums.StatusContractExpired:
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
if cert.Status != enums.StatusInfoPendingReview {
|
if err := s.rejectEnterpriseInfoWithCleanup(ctx, cert, record, adminID, remark); err != nil {
|
||||||
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
return err
|
||||||
}
|
|
||||||
if err := cert.RejectEnterpriseInfoReview(adminID, remark); err != nil {
|
|
||||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
|
||||||
}
|
|
||||||
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
|
||||||
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
||||||
}
|
|
||||||
record.MarkManualRejected(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))
|
s.logger.Info("管理员审核拒绝企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
||||||
return nil
|
return nil
|
||||||
@@ -1137,23 +1112,19 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus
|
|||||||
s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
s.logger.Info("管理端变更认证状态为通过", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
||||||
return nil
|
return nil
|
||||||
case string(enums.StatusInfoRejected):
|
case string(enums.StatusInfoRejected):
|
||||||
// 审核拒绝
|
if cmd.Remark == "" {
|
||||||
|
return fmt.Errorf("拒绝时必须填写审核备注")
|
||||||
|
}
|
||||||
if cert.Status == enums.StatusInfoRejected || cert.Status == enums.StatusEnterpriseVerified ||
|
if cert.Status == enums.StatusInfoRejected || cert.Status == enums.StatusEnterpriseVerified ||
|
||||||
cert.Status == enums.StatusContractApplied || cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
cert.Status == enums.StatusContractApplied || cert.Status == enums.StatusContractSigned || cert.Status == enums.StatusCompleted {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
if cert.Status != enums.StatusInfoPendingReview {
|
verifiedRecord, verr := s.enterpriseInfoSubmitRecordRepo.FindLatestVerifiedByUserID(ctx, cert.UserID)
|
||||||
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
if verr != nil {
|
||||||
|
return fmt.Errorf("查找该用户有效的企业信息提交记录失败: %w", verr)
|
||||||
}
|
}
|
||||||
if err := cert.RejectEnterpriseInfoReview(cmd.AdminID, cmd.Remark); err != nil {
|
if err := s.rejectEnterpriseInfoWithCleanup(ctx, cert, verifiedRecord, cmd.AdminID, cmd.Remark); err != nil {
|
||||||
return fmt.Errorf("更新认证状态失败: %w", err)
|
return err
|
||||||
}
|
|
||||||
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
|
||||||
return fmt.Errorf("保存认证信息失败: %w", err)
|
|
||||||
}
|
|
||||||
record.MarkManualRejected(cmd.AdminID, cmd.Remark)
|
|
||||||
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
|
||||||
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
|
||||||
}
|
}
|
||||||
s.logger.Info("管理端变更认证状态为拒绝", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
s.logger.Info("管理端变更认证状态为拒绝", zap.String("user_id", cmd.UserID), zap.String("admin_id", cmd.AdminID))
|
||||||
return nil
|
return nil
|
||||||
@@ -1164,6 +1135,51 @@ func (s *CertificationApplicationServiceImpl) AdminTransitionCertificationStatus
|
|||||||
|
|
||||||
// ================ 辅助方法 ================
|
// ================ 辅助方法 ================
|
||||||
|
|
||||||
|
// rejectEnterpriseInfoWithCleanup 管理员拒绝企业信息:同步更新认证状态与提交记录(仅释放该用户自己的 USCC 占用)
|
||||||
|
func (s *CertificationApplicationServiceImpl) rejectEnterpriseInfoWithCleanup(
|
||||||
|
ctx context.Context,
|
||||||
|
cert *entities.Certification,
|
||||||
|
record *entities.EnterpriseInfoSubmitRecord,
|
||||||
|
adminID, remark string,
|
||||||
|
) error {
|
||||||
|
if remark == "" {
|
||||||
|
return fmt.Errorf("拒绝时必须填写审核备注")
|
||||||
|
}
|
||||||
|
if record.UserID != cert.UserID {
|
||||||
|
return fmt.Errorf("提交记录与认证用户不匹配,无法拒绝")
|
||||||
|
}
|
||||||
|
if record.Status != "verified" {
|
||||||
|
return fmt.Errorf("该条提交记录未通过前置校验或已失败,无法从后台拒绝(请查看历史失败原因)")
|
||||||
|
}
|
||||||
|
switch cert.Status {
|
||||||
|
case enums.StatusInfoRejected,
|
||||||
|
enums.StatusEnterpriseVerified,
|
||||||
|
enums.StatusContractApplied,
|
||||||
|
enums.StatusContractSigned,
|
||||||
|
enums.StatusCompleted,
|
||||||
|
enums.StatusContractRejected,
|
||||||
|
enums.StatusContractExpired:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if !enums.CanAdminRejectEnterpriseInfoPhase(cert.Status) {
|
||||||
|
return fmt.Errorf("当前认证已进入企业认证/合同阶段,不可拒绝企业信息")
|
||||||
|
}
|
||||||
|
if err := cert.RejectEnterpriseInfoReview(adminID, remark); err != nil {
|
||||||
|
return fmt.Errorf("更新认证状态失败: %w", err)
|
||||||
|
}
|
||||||
|
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
||||||
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
||||||
|
}
|
||||||
|
updated, err := s.enterpriseInfoSubmitRecordRepo.MarkRejectedAndFailedForUser(ctx, record.ID, cert.UserID, adminID, remark)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("保存企业信息提交记录失败: %w", err)
|
||||||
|
}
|
||||||
|
if !updated {
|
||||||
|
return fmt.Errorf("未找到该用户对应的有效提交记录,USCC 占用未释放")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// convertToResponse 转换实体为响应DTO
|
// convertToResponse 转换实体为响应DTO
|
||||||
func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.Certification) *responses.CertificationResponse {
|
func (s *CertificationApplicationServiceImpl) convertToResponse(cert *entities.Certification) *responses.CertificationResponse {
|
||||||
response := &responses.CertificationResponse{
|
response := &responses.CertificationResponse{
|
||||||
|
|||||||
@@ -227,12 +227,20 @@ func (c *Certification) ApproveEnterpriseInfoReview(authURL, authFlowID string,
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// RejectEnterpriseInfoReview 管理员审核拒绝
|
// RejectEnterpriseInfoReview 管理员拒绝企业信息(info_pending_review 或 info_submitted)
|
||||||
func (c *Certification) RejectEnterpriseInfoReview(actorID, message string) error {
|
func (c *Certification) RejectEnterpriseInfoReview(actorID, message string) error {
|
||||||
if c.Status != enums.StatusInfoPendingReview {
|
if !enums.CanAdminRejectEnterpriseInfoPhase(c.Status) {
|
||||||
return fmt.Errorf("当前状态 %s 不允许执行审核拒绝", enums.GetStatusName(c.Status))
|
return fmt.Errorf("当前认证已进入企业认证/合同阶段,不可拒绝企业信息")
|
||||||
|
}
|
||||||
|
failureReason := enums.FailureReasonManualReviewRejected
|
||||||
|
if c.Status == enums.StatusInfoSubmitted {
|
||||||
|
failureReason = enums.FailureReasonEsignVerificationFailed
|
||||||
|
}
|
||||||
|
c.setFailureInfo(failureReason, message)
|
||||||
|
if c.Status == enums.StatusInfoSubmitted {
|
||||||
|
c.AuthURL = ""
|
||||||
|
c.AuthFlowID = ""
|
||||||
}
|
}
|
||||||
c.setFailureInfo(enums.FailureReasonManualReviewRejected, message)
|
|
||||||
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeAdmin, actorID, "管理员审核拒绝"); err != nil {
|
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeAdmin, actorID, "管理员审核拒绝"); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -242,7 +242,8 @@ func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStat
|
|||||||
// 最终状态,无后续状态
|
// 最终状态,无后续状态
|
||||||
},
|
},
|
||||||
StatusInfoRejected: {
|
StatusInfoRejected: {
|
||||||
StatusInfoSubmitted, // 可以重新提交
|
StatusInfoPendingReview, // 用户修正后重新提交,进入人工审核
|
||||||
|
StatusInfoSubmitted, // 兼容旧路径:直接重新提交
|
||||||
// 管理员/系统可直接标记为完成
|
// 管理员/系统可直接标记为完成
|
||||||
StatusCompleted,
|
StatusCompleted,
|
||||||
},
|
},
|
||||||
@@ -289,6 +290,7 @@ func GetTransitionReason(from, to CertificationStatus) string {
|
|||||||
string(StatusContractSigned) + "->" + string(StatusCompleted): "系统处理完成,认证成功",
|
string(StatusContractSigned) + "->" + string(StatusCompleted): "系统处理完成,认证成功",
|
||||||
string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同",
|
string(StatusContractApplied) + "->" + string(StatusContractRejected): "用户拒绝签署合同",
|
||||||
string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时",
|
string(StatusContractApplied) + "->" + string(StatusContractExpired): "合同签署超时",
|
||||||
|
string(StatusInfoRejected) + "->" + string(StatusInfoPendingReview): "用户修正后重新提交企业信息",
|
||||||
string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息",
|
string(StatusInfoRejected) + "->" + string(StatusInfoSubmitted): "用户重新提交企业信息",
|
||||||
string(StatusContractRejected) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
|
string(StatusContractRejected) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
|
||||||
string(StatusContractExpired) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
|
string(StatusContractExpired) + "->" + string(StatusEnterpriseVerified): "重置状态,准备重新申请",
|
||||||
@@ -300,3 +302,8 @@ func GetTransitionReason(from, to CertificationStatus) string {
|
|||||||
}
|
}
|
||||||
return "未知转换"
|
return "未知转换"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// CanAdminRejectEnterpriseInfoPhase 是否允许管理员拒绝企业信息(仅 early phase)
|
||||||
|
func CanAdminRejectEnterpriseInfoPhase(status CertificationStatus) bool {
|
||||||
|
return status == StatusInfoPendingReview || status == StatusInfoSubmitted
|
||||||
|
}
|
||||||
|
|||||||
@@ -30,5 +30,7 @@ type EnterpriseInfoSubmitRecordRepository interface {
|
|||||||
FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
||||||
// ExistsByUnifiedSocialCodeExcludeUser 检查该统一社会信用代码是否已被其他用户提交(已提交/已通过验证,排除指定用户)
|
// ExistsByUnifiedSocialCodeExcludeUser 检查该统一社会信用代码是否已被其他用户提交(已提交/已通过验证,排除指定用户)
|
||||||
ExistsByUnifiedSocialCodeExcludeUser(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error)
|
ExistsByUnifiedSocialCodeExcludeUser(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error)
|
||||||
|
// MarkRejectedAndFailedForUser 仅当提交记录属于指定用户且为 verified 时,标记人工拒绝并 failed(释放该用户占用的 USCC)
|
||||||
|
MarkRejectedAndFailedForUser(ctx context.Context, recordID, userID, reviewerID, remark string) (updated bool, err error)
|
||||||
List(ctx context.Context, filter ListSubmitRecordsFilter) (*ListSubmitRecordsResult, error)
|
List(ctx context.Context, filter ListSubmitRecordsFilter) (*ListSubmitRecordsResult, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package certification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"time"
|
||||||
|
|
||||||
"tyapi-server/internal/domains/certification/entities"
|
"tyapi-server/internal/domains/certification/entities"
|
||||||
"tyapi-server/internal/domains/certification/repositories"
|
"tyapi-server/internal/domains/certification/repositories"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
@@ -90,6 +92,33 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) ExistsByUnifiedSocialCodeExcl
|
|||||||
return count > 0, nil
|
return count > 0, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkRejectedAndFailedForUser 仅当 id 与 user_id 均匹配且 status=verified 时更新,避免误释放其他用户的 USCC 占用
|
||||||
|
func (r *GormEnterpriseInfoSubmitRecordRepository) MarkRejectedAndFailedForUser(
|
||||||
|
ctx context.Context,
|
||||||
|
recordID, userID, reviewerID, remark string,
|
||||||
|
) (bool, error) {
|
||||||
|
if recordID == "" || userID == "" {
|
||||||
|
return false, nil
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
result := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}).
|
||||||
|
Where("id = ? AND user_id = ? AND status = ?", recordID, userID, "verified").
|
||||||
|
Updates(map[string]interface{}{
|
||||||
|
"status": "failed",
|
||||||
|
"failed_at": now,
|
||||||
|
"failure_reason": remark,
|
||||||
|
"manual_review_status": "rejected",
|
||||||
|
"manual_reviewed_at": now,
|
||||||
|
"manual_reviewer_id": reviewerID,
|
||||||
|
"manual_review_remark": remark,
|
||||||
|
"updated_at": now,
|
||||||
|
})
|
||||||
|
if result.Error != nil {
|
||||||
|
return false, result.Error
|
||||||
|
}
|
||||||
|
return result.RowsAffected > 0, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, filter repositories.ListSubmitRecordsFilter) (*repositories.ListSubmitRecordsResult, error) {
|
func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, filter repositories.ListSubmitRecordsFilter) (*repositories.ListSubmitRecordsResult, error) {
|
||||||
base := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{})
|
base := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{})
|
||||||
if filter.CertificationStatus != "" {
|
if filter.CertificationStatus != "" {
|
||||||
|
|||||||
Reference in New Issue
Block a user