diff --git a/internal/application/certification/certification_application_service_impl.go b/internal/application/certification/certification_application_service_impl.go index 5566772..fba0f7c 100644 --- a/internal/application/certification/certification_application_service_impl.go +++ b/internal/application/certification/certification_application_service_impl.go @@ -109,6 +109,12 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( s.logger.Info("开始提交企业信息", zap.String("user_id", cmd.UserID)) + // 0. 若该用户已有待审核的提交记录,则不允许重复提交 + latestRecord, err := s.enterpriseInfoSubmitRecordRepo.FindLatestByUserID(ctx, cmd.UserID) + if err == nil && latestRecord != nil && latestRecord.ManualReviewStatus == "pending" { + return nil, fmt.Errorf("您已有待审核的提交,请等待管理员审核后再操作") + } + // 1.5 插入企业信息提交记录(包含扩展字段) record := entities.NewEnterpriseInfoSubmitRecord( cmd.UserID, @@ -164,7 +170,8 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( } s.logger.Info("开始处理企业信息提交", zap.String("user_id", cmd.UserID)) - // 1. 检查企业信息是否重复(统一社会信用代码,已经认证了的,不能重复提交) + // 1. 检查企业信息是否重复(统一社会信用代码:已认证或已提交待审核的都不能重复) + // 1.1 已写入用户域 enterprise_infos 的(已完成认证) exists, err := s.userAggregateService.CheckUnifiedSocialCodeExists(ctx, cmd.UnifiedSocialCode, cmd.UserID) if err != nil { record.MarkAsFailed(err.Error()) @@ -174,7 +181,6 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( } return nil, fmt.Errorf("检查企业信息失败: %s", err.Error()) } - if exists { record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确") saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record) @@ -182,7 +188,24 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error()) } return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确") - + } + // 1.2 已提交/已通过验证的提交记录(尚未完成认证但已占用的信用代码) + existsInSubmit, err := s.enterpriseInfoSubmitRecordRepo.ExistsByUnifiedSocialCodeExcludeUser(ctx, cmd.UnifiedSocialCode, cmd.UserID) + if err != nil { + record.MarkAsFailed(err.Error()) + saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record) + if saveErr != nil { + return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error()) + } + return nil, fmt.Errorf("检查企业信息失败: %s", err.Error()) + } + if existsInSubmit { + record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确") + saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record) + if saveErr != nil { + return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error()) + } + return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确") } enterpriseInfo := &certification_value_objects.EnterpriseInfo{ @@ -214,10 +237,6 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return nil, fmt.Errorf("企业信息验证失败, %s", err.Error()) } record.MarkAsVerified() - saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record) - if saveErr != nil { - return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error()) - } var response *responses.CertificationResponse err = s.txManager.ExecuteInTx(ctx, func(txCtx context.Context) error { @@ -255,23 +274,25 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo( return fmt.Errorf("提交企业信息失败: %s", err.Error()) } - respMeta := map[string]interface{}{ - "enterprise_info": enterpriseInfo, - "next_action": "请等待管理员审核企业信息", - } - err = s.aggregateService.SaveCertification(txCtx, cert) if err != nil { return fmt.Errorf("保存认证信息失败: %s", err.Error()) } - // 5. 转换为响应DTO - response = s.convertToResponse(cert) - // 6. 添加工作流结果信息 + // 5. 提交记录与认证状态在同一事务内保存,避免出现「有记录但认证未变待审核」的不一致 + if saveErr := s.enterpriseInfoSubmitRecordService.Save(txCtx, record); saveErr != nil { + return fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error()) + } + + respMeta := map[string]interface{}{ + "enterprise_info": enterpriseInfo, + "next_action": "请等待管理员审核企业信息", + } + // 6. 转换为响应 DTO + response = s.convertToResponse(cert) if respMeta != nil { response.Metadata = respMeta } - return nil }) if err != nil { @@ -804,8 +825,18 @@ func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx conte if err != nil { return fmt.Errorf("加载认证信息失败: %w", err) } + // 兼容线上脏数据:提交记录已落库但当时事务失败导致认证仍为「待认证」,先同步为待审核再执行通过 if cert.Status != enums.StatusInfoPendingReview { - return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) + 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)) + } } enterpriseInfo := &certification_value_objects.EnterpriseInfo{ CompanyName: record.CompanyName, @@ -857,8 +888,18 @@ func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx contex if err != nil { return fmt.Errorf("加载认证信息失败: %w", err) } + // 兼容线上脏数据:提交记录已落库但当时事务失败导致认证仍为「待认证」,先同步为待审核再执行拒绝 if cert.Status != enums.StatusInfoPendingReview { - return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status)) + 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)) + } } record.MarkManualRejected(adminID, remark) if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil { @@ -876,6 +917,33 @@ func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx contex // ================ 辅助方法 ================ +// syncCertToPendingReviewIfRecordPending 兼容历史脏数据:当认证为「待认证」或「已拒绝」且存在待审核提交记录时, +// 用该记录的企业信息把认证同步为「待审核」,便于管理员直接审核通过/拒绝。 +func (s *CertificationApplicationServiceImpl) syncCertToPendingReviewIfRecordPending(ctx context.Context, cert *entities.Certification, record *entities.EnterpriseInfoSubmitRecord) error { + if record.ManualReviewStatus != "pending" { + return nil + } + 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/config/config.go b/internal/config/config.go index e865834..411fcd1 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -38,10 +38,10 @@ type Config struct { TianYanCha TianYanChaConfig `mapstructure:"tianyancha"` Alicloud AlicloudConfig `mapstructure:"alicloud"` Xingwei XingweiConfig `mapstructure:"xingwei"` - Jiguang JiguangConfig `mapstructure:"jiguang"` - Shumai ShumaiConfig `mapstructure:"shumai"` - Shujubao ShujubaoConfig `mapstructure:"shujubao"` - PDFGen PDFGenConfig `mapstructure:"pdfgen"` + Jiguang JiguangConfig `mapstructure:"jiguang"` + Shumai ShumaiConfig `mapstructure:"shumai"` + Shujubao ShujubaoConfig `mapstructure:"shujubao"` + PDFGen PDFGenConfig `mapstructure:"pdfgen"` } // ServerConfig HTTP服务器配置 @@ -217,10 +217,10 @@ type SMSConfig struct { SignatureEnabled bool `mapstructure:"signature_enabled"` // 是否启用签名验证 SignatureSecret string `mapstructure:"signature_secret"` // 签名密钥 // 滑块验证码配置 - CaptchaEnabled bool `mapstructure:"captcha_enabled"` // 是否启用滑块验证码 - CaptchaSecret string `mapstructure:"captcha_secret"` // 阿里云验证码密钥 - CaptchaEndpoint string `mapstructure:"captcha_endpoint"` // 阿里云验证码服务Endpoint - SceneID string `mapstructure:"scene_id"` // 阿里云验证码场景ID + CaptchaEnabled bool `mapstructure:"captcha_enabled"` // 是否启用滑块验证码 + CaptchaSecret string `mapstructure:"captcha_secret"` // 阿里云验证码密钥 + CaptchaEndpoint string `mapstructure:"captcha_endpoint"` // 阿里云验证码服务Endpoint + SceneID string `mapstructure:"scene_id"` // 阿里云验证码场景ID } // SMSRateLimit 短信限流配置 @@ -332,10 +332,10 @@ type SignConfig struct { // WalletConfig 钱包配置 type WalletConfig struct { DefaultCreditLimit float64 `mapstructure:"default_credit_limit"` - MinAmount string `mapstructure:"min_amount"` // 最低充值金额 - MaxAmount string `mapstructure:"max_amount"` // 最高充值金额 + MinAmount string `mapstructure:"min_amount"` // 最低充值金额 + MaxAmount string `mapstructure:"max_amount"` // 最高充值金额 RechargeBonusEnabled bool `mapstructure:"recharge_bonus_enabled"` // 是否启用充值赠送,关闭后仅展示商务洽谈提示 - ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务) + ApiStoreRechargeTip string `mapstructure:"api_store_recharge_tip"` // API 商店充值提示文案(大额/批量需求联系商务) AliPayRechargeBonus []AliPayRechargeBonusRule `mapstructure:"alipay_recharge_bonus"` BalanceAlert BalanceAlertConfig `mapstructure:"balance_alert"` } @@ -578,10 +578,10 @@ type ShumaiConfig struct { // ShumaiLoggingConfig 数脉日志配置 type ShumaiLoggingConfig struct { - Enabled bool `mapstructure:"enabled"` - LogDir string `mapstructure:"log_dir"` - UseDaily bool `mapstructure:"use_daily"` - EnableLevelSeparation bool `mapstructure:"enable_level_separation"` + Enabled bool `mapstructure:"enabled"` + LogDir string `mapstructure:"log_dir"` + UseDaily bool `mapstructure:"use_daily"` + EnableLevelSeparation bool `mapstructure:"enable_level_separation"` LevelConfigs map[string]ShumaiLevelFileConfig `mapstructure:"level_configs"` } @@ -605,10 +605,10 @@ type ShujubaoConfig struct { // ShujubaoLoggingConfig 数据宝日志配置 type ShujubaoLoggingConfig struct { - Enabled bool `mapstructure:"enabled"` - LogDir string `mapstructure:"log_dir"` - UseDaily bool `mapstructure:"use_daily"` - EnableLevelSeparation bool `mapstructure:"enable_level_separation"` + Enabled bool `mapstructure:"enabled"` + LogDir string `mapstructure:"log_dir"` + UseDaily bool `mapstructure:"use_daily"` + EnableLevelSeparation bool `mapstructure:"enable_level_separation"` LevelConfigs map[string]ShujubaoLevelFileConfig `mapstructure:"level_configs"` } @@ -622,11 +622,11 @@ type ShujubaoLevelFileConfig struct { // PDFGenConfig PDF生成服务配置 type PDFGenConfig struct { - DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址 - ProductionURL string `mapstructure:"production_url"` // 生产环境服务地址 - APIPath string `mapstructure:"api_path"` // API路径 - Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间 - Cache PDFGenCacheConfig `mapstructure:"cache"` // 缓存配置 + DevelopmentURL string `mapstructure:"development_url"` // 开发环境服务地址 + ProductionURL string `mapstructure:"production_url"` // 生产环境服务地址 + APIPath string `mapstructure:"api_path"` // API路径 + Timeout time.Duration `mapstructure:"timeout"` // 请求超时时间 + Cache PDFGenCacheConfig `mapstructure:"cache"` // 缓存配置 } // PDFGenCacheConfig PDF生成缓存配置 diff --git a/internal/domains/api/dto/api_request_dto.go b/internal/domains/api/dto/api_request_dto.go index a8f62f7..47223ed 100644 --- a/internal/domains/api/dto/api_request_dto.go +++ b/internal/domains/api/dto/api_request_dto.go @@ -616,6 +616,15 @@ type QYGLJ0Q1Req struct { EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"` EntCode string `json:"ent_code" validate:"omitempty,validUSCI"` } + +type QYGLDJG3Req struct { + IDCard string `json:"id_card" validate:"required,validIDCard"` +} +type QYGLDJ12Req struct { + EntName string `json:"ent_name" validate:"omitempty,min=1,validEnterpriseName"` + EntCode string `json:"ent_code" validate:"omitempty,validUSCI"` + EntRegNo string `json:"ent_reg_no" validate:"omitempty,min=1,validEntRegNo"` +} type YYSY6D9AReq struct { MobileNo string `json:"mobile_no" validate:"required,min=11,max=11,validMobileNo"` IDCard string `json:"id_card" validate:"required,validIDCard"` diff --git a/internal/domains/api/services/processors/flxg/qygldjg3_processor.go b/internal/domains/api/services/processors/flxg/qygldjg3_processor.go new file mode 100644 index 0000000..33f9d72 --- /dev/null +++ b/internal/domains/api/services/processors/flxg/qygldjg3_processor.go @@ -0,0 +1,54 @@ +package flxg + +import ( + "context" + "encoding/json" + "errors" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/shujubao" +) + +// ProcessQYGLDJG3Request QYGLDJG3 董监高司法综合信息核验 API 处理方法(使用数据宝服务示例) +func ProcessQYGLDJG3Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.QYGLDJG3Req + if err := json.Unmarshal(params, ¶msDto); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + if err := deps.Validator.ValidateStruct(paramsDto); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + + // 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData) + reqParams := map[string]interface{}{ + "key": "1cce582f0a6f3ca40de80f1bea9b9698", + "idcard": paramsDto.IDCard, + } + + // 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197 + apiPath := "/communication/personal/10166" + data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams) + if err != nil { + if errors.Is(err, shujubao.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } + if errors.Is(err, shujubao.ErrQueryEmpty) { + return nil, errors.Join(processors.ErrNotFound, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + // 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse) + parsedResp, err := RecursiveParse(data) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err := json.Marshal(parsedResp) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return respBytes, nil +} diff --git a/internal/domains/api/services/processors/qygl/qygl8848_processor.go b/internal/domains/api/services/processors/qygl/qygl8848_processor.go new file mode 100644 index 0000000..c333a0c --- /dev/null +++ b/internal/domains/api/services/processors/qygl/qygl8848_processor.go @@ -0,0 +1,68 @@ +package qygl + +import ( + "context" + "encoding/json" + "errors" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/shujubao" +) + +// ProcessQYGL8848Request QYGL8848 企业税收违法核查 API 处理方法(使用数据宝服务示例) +func ProcessQYGL8848Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.QYGLDJ12Req + if err := json.Unmarshal(params, ¶msDto); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + if err := deps.Validator.ValidateStruct(paramsDto); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + + // 三选一:企业名称(entName) 与 统一社会信用代码(creditCode) 与 企业注册号(entRegNo) 必须且仅能传其一 + hasEntName := paramsDto.EntName != "" + hasEntCode := paramsDto.EntCode != "" + hasEntRegNo := paramsDto.EntRegNo != "" + if hasEntName == hasEntCode == hasEntRegNo { // 三个都填或三个都未填 + return nil, errors.Join(processors.ErrInvalidParam, errors.New("ent_name 与 ent_code 与 ent_reg_no 三选一,必须且仅能传其中一个")) + } + + // 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData) + reqParams := map[string]interface{}{ + "key": "c67673dd2e92deb2d2ec91b87bb0a81c", + } + if hasEntName { + reqParams["entName"] = paramsDto.EntName + } else if hasEntCode { + reqParams["creditCode"] = paramsDto.EntCode + } else if hasEntRegNo { + reqParams["regCode"] = paramsDto.EntRegNo + } + + // 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197 + apiPath := "/communication/personal/10233" + data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams) + if err != nil { + if errors.Is(err, shujubao.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } + if errors.Is(err, shujubao.ErrQueryEmpty) { + return nil, errors.Join(processors.ErrNotFound, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + // 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse) + parsedResp, err := RecursiveParse(data) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err := json.Marshal(parsedResp) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return respBytes, nil +} diff --git a/internal/domains/api/services/processors/qygl/qygldj12_processor.go b/internal/domains/api/services/processors/qygl/qygldj12_processor.go new file mode 100644 index 0000000..69d1e45 --- /dev/null +++ b/internal/domains/api/services/processors/qygl/qygldj12_processor.go @@ -0,0 +1,68 @@ +package qygl + +import ( + "context" + "encoding/json" + "errors" + + "tyapi-server/internal/domains/api/dto" + "tyapi-server/internal/domains/api/services/processors" + "tyapi-server/internal/infrastructure/external/shujubao" +) + +// ProcessQYGLDJ12Request QYGLDJ12 企业年报信息核验 API 处理方法(使用数据宝服务示例) +func ProcessQYGLDJ12Request(ctx context.Context, params []byte, deps *processors.ProcessorDependencies) ([]byte, error) { + var paramsDto dto.QYGLDJ12Req + if err := json.Unmarshal(params, ¶msDto); err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + if err := deps.Validator.ValidateStruct(paramsDto); err != nil { + return nil, errors.Join(processors.ErrInvalidParam, err) + } + + // 三选一:企业名称(entName) 与 统一社会信用代码(creditCode) 与 企业注册号(entRegNo) 必须且仅能传其一 + hasEntName := paramsDto.EntName != "" + hasEntCode := paramsDto.EntCode != "" + hasEntRegNo := paramsDto.EntRegNo != "" + if hasEntName == hasEntCode == hasEntRegNo { // 三个都填或三个都未填 + return nil, errors.Join(processors.ErrInvalidParam, errors.New("ent_name 与 ent_code 与 ent_reg_no 三选一,必须且仅能传其中一个")) + } + + // 构建数据宝入参(sign 外的业务参数可按需 AES 加密后作为 bodyData) + reqParams := map[string]interface{}{ + "key": "112813815e2cc281ad8f552deb7a3c7f", + } + if hasEntName { + reqParams["entName"] = paramsDto.EntName + } else if hasEntCode { + reqParams["creditCode"] = paramsDto.EntCode + } else if hasEntRegNo { + reqParams["regCode"] = paramsDto.EntRegNo + } + + // 最终请求 URL = https://api.chinadatapay.com/communication + 拼接接口地址值,如 personal/197 + apiPath := "/communication/personal/10192" + data, err := deps.ShujubaoService.CallAPI(ctx, apiPath, reqParams) + if err != nil { + if errors.Is(err, shujubao.ErrDatasource) { + return nil, errors.Join(processors.ErrDatasource, err) + } + if errors.Is(err, shujubao.ErrQueryEmpty) { + return nil, errors.Join(processors.ErrNotFound, err) + } + return nil, errors.Join(processors.ErrSystem, err) + } + + // 解析响应中的 JSON 字符串(使用 qyglb4c0 中的 RecursiveParse) + parsedResp, err := RecursiveParse(data) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + + respBytes, err := json.Marshal(parsedResp) + if err != nil { + return nil, errors.Join(processors.ErrSystem, err) + } + return respBytes, nil +} 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 77bf909..741a4b5 100644 --- a/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go +++ b/internal/domains/certification/repositories/enterprise_info_submit_record_repository.go @@ -25,5 +25,7 @@ type EnterpriseInfoSubmitRecordRepository interface { FindByID(ctx context.Context, id string) (*entities.EnterpriseInfoSubmitRecord, error) FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) + // ExistsByUnifiedSocialCodeExcludeUser 检查该统一社会信用代码是否已被其他用户提交(已提交/已通过验证,排除指定用户) + ExistsByUnifiedSocialCodeExcludeUser(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error) List(ctx context.Context, filter ListSubmitRecordsFilter) (*ListSubmitRecordsResult, error) } 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 8dee97c..f82042b 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 @@ -73,6 +73,23 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) FindLatestVerifiedByUserID(ct return &record, nil } +// ExistsByUnifiedSocialCodeExcludeUser 检查该统一社会信用代码是否已被其他用户占用(已提交或已通过验证的记录) +func (r *GormEnterpriseInfoSubmitRecordRepository) ExistsByUnifiedSocialCodeExcludeUser(ctx context.Context, unifiedSocialCode string, excludeUserID string) (bool, error) { + if unifiedSocialCode == "" { + return false, nil + } + var count int64 + query := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}). + Where("unified_social_code = ? AND status IN (?, ?)", unifiedSocialCode, "submitted", "verified") + if excludeUserID != "" { + query = query.Where("user_id != ?", excludeUserID) + } + if err := query.Count(&count).Error; err != nil { + return false, err + } + return count > 0, nil +} + func (r *GormEnterpriseInfoSubmitRecordRepository) List(ctx context.Context, filter repositories.ListSubmitRecordsFilter) (*repositories.ListSubmitRecordsResult, error) { db := r.GetDB(ctx).Model(&entities.EnterpriseInfoSubmitRecord{}) if filter.ManualReviewStatus != "" {