add
This commit is contained in:
@@ -37,6 +37,15 @@ type CertificationApplicationService interface {
|
|||||||
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
// AdminCompleteCertificationWithoutContract 管理员代用户完成认证(暂不关联合同)
|
||||||
AdminCompleteCertificationWithoutContract(ctx context.Context, cmd *commands.AdminCompleteCertificationCommand) (*responses.CertificationResponse, error)
|
AdminCompleteCertificationWithoutContract(ctx context.Context, cmd *commands.AdminCompleteCertificationCommand) (*responses.CertificationResponse, error)
|
||||||
|
|
||||||
|
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
||||||
|
AdminListSubmitRecords(ctx context.Context, query *queries.AdminListSubmitRecordsQuery) (*responses.AdminSubmitRecordsListResponse, error)
|
||||||
|
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
||||||
|
AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error)
|
||||||
|
// AdminApproveSubmitRecord 管理端审核通过
|
||||||
|
AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
|
||||||
|
// AdminRejectSubmitRecord 管理端审核拒绝
|
||||||
|
AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error
|
||||||
|
|
||||||
// ================ e签宝回调处理 ================
|
// ================ e签宝回调处理 ================
|
||||||
|
|
||||||
// 处理e签宝回调
|
// 处理e签宝回调
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package certification
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -10,6 +11,7 @@ import (
|
|||||||
"tyapi-server/internal/application/certification/dto/commands"
|
"tyapi-server/internal/application/certification/dto/commands"
|
||||||
"tyapi-server/internal/application/certification/dto/queries"
|
"tyapi-server/internal/application/certification/dto/queries"
|
||||||
"tyapi-server/internal/application/certification/dto/responses"
|
"tyapi-server/internal/application/certification/dto/responses"
|
||||||
|
"tyapi-server/internal/config"
|
||||||
api_service "tyapi-server/internal/domains/api/services"
|
api_service "tyapi-server/internal/domains/api/services"
|
||||||
"tyapi-server/internal/domains/certification/entities"
|
"tyapi-server/internal/domains/certification/entities"
|
||||||
certification_value_objects "tyapi-server/internal/domains/certification/entities/value_objects"
|
certification_value_objects "tyapi-server/internal/domains/certification/entities/value_objects"
|
||||||
@@ -19,7 +21,6 @@ import (
|
|||||||
finance_service "tyapi-server/internal/domains/finance/services"
|
finance_service "tyapi-server/internal/domains/finance/services"
|
||||||
user_entities "tyapi-server/internal/domains/user/entities"
|
user_entities "tyapi-server/internal/domains/user/entities"
|
||||||
user_service "tyapi-server/internal/domains/user/services"
|
user_service "tyapi-server/internal/domains/user/services"
|
||||||
"tyapi-server/internal/config"
|
|
||||||
"tyapi-server/internal/infrastructure/external/notification"
|
"tyapi-server/internal/infrastructure/external/notification"
|
||||||
"tyapi-server/internal/infrastructure/external/storage"
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
@@ -51,6 +52,7 @@ type CertificationApplicationServiceImpl struct {
|
|||||||
|
|
||||||
wechatWorkService *notification.WeChatWorkService
|
wechatWorkService *notification.WeChatWorkService
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
|
config *config.Config
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificationApplicationService 创建认证应用服务
|
// NewCertificationApplicationService 创建认证应用服务
|
||||||
@@ -93,6 +95,7 @@ func NewCertificationApplicationService(
|
|||||||
txManager: txManager,
|
txManager: txManager,
|
||||||
wechatWorkService: wechatSvc,
|
wechatWorkService: wechatSvc,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
config: cfg,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,7 +109,7 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
s.logger.Info("开始提交企业信息",
|
s.logger.Info("开始提交企业信息",
|
||||||
zap.String("user_id", cmd.UserID))
|
zap.String("user_id", cmd.UserID))
|
||||||
|
|
||||||
// 1.5 插入企业信息提交记录
|
// 1.5 插入企业信息提交记录(包含扩展字段)
|
||||||
record := entities.NewEnterpriseInfoSubmitRecord(
|
record := entities.NewEnterpriseInfoSubmitRecord(
|
||||||
cmd.UserID,
|
cmd.UserID,
|
||||||
cmd.CompanyName,
|
cmd.CompanyName,
|
||||||
@@ -117,6 +120,36 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
cmd.EnterpriseAddress,
|
cmd.EnterpriseAddress,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// 扩展字段赋值
|
||||||
|
record.BusinessLicenseImageURL = cmd.BusinessLicenseImageURL
|
||||||
|
if len(cmd.OfficePlaceImageURLs) > 0 {
|
||||||
|
if data, mErr := json.Marshal(cmd.OfficePlaceImageURLs); mErr == nil {
|
||||||
|
record.OfficePlaceImageURLs = string(data)
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("序列化办公场地图片URL失败", zap.Error(mErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record.APIUsage = cmd.APIUsage
|
||||||
|
if len(cmd.ScenarioAttachmentURLs) > 0 {
|
||||||
|
if data, mErr := json.Marshal(cmd.ScenarioAttachmentURLs); mErr == nil {
|
||||||
|
record.ScenarioAttachmentURLs = string(data)
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("序列化场景附件图片URL失败", zap.Error(mErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 授权代表信息落库
|
||||||
|
record.AuthorizedRepName = cmd.AuthorizedRepName
|
||||||
|
record.AuthorizedRepID = cmd.AuthorizedRepID
|
||||||
|
record.AuthorizedRepPhone = cmd.AuthorizedRepPhone
|
||||||
|
if len(cmd.AuthorizedRepIDImageURLs) > 0 {
|
||||||
|
if data, mErr := json.Marshal(cmd.AuthorizedRepIDImageURLs); mErr == nil {
|
||||||
|
record.AuthorizedRepIDImageURLs = string(data)
|
||||||
|
} else {
|
||||||
|
s.logger.Warn("序列化授权代表身份证图片URL失败", zap.Error(mErr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 验证验证码
|
// 验证验证码
|
||||||
// 特殊验证码"768005"直接跳过验证环节
|
// 特殊验证码"768005"直接跳过验证环节
|
||||||
if cmd.VerificationCode != "768005" {
|
if cmd.VerificationCode != "768005" {
|
||||||
@@ -141,6 +174,7 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
}
|
}
|
||||||
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
return nil, fmt.Errorf("检查企业信息失败: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if exists {
|
if exists {
|
||||||
record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
record.MarkAsFailed("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
||||||
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
saveErr := s.enterpriseInfoSubmitRecordService.Save(ctx, record)
|
||||||
@@ -148,6 +182,7 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
return nil, fmt.Errorf("保存企业信息提交记录失败: %s", saveErr.Error())
|
||||||
}
|
}
|
||||||
return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
return nil, fmt.Errorf("该企业信息已被其他用户使用,请确认企业信息是否正确")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
enterpriseInfo := &certification_value_objects.EnterpriseInfo{
|
||||||
@@ -205,56 +240,24 @@ func (s *CertificationApplicationServiceImpl) SubmitEnterpriseInfo(
|
|||||||
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
return fmt.Errorf("加载认证信息失败: %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 调用e签宝看是否进行过认证
|
// 已是「已提交企业信息」:说明已通过人工审核,直接返回当前认证数据,前端可刷新到企业认证步骤
|
||||||
respMeta := map[string]interface{}{}
|
if cert.Status == enums.StatusInfoSubmitted {
|
||||||
|
response = s.convertToResponse(cert)
|
||||||
identity, err := s.esignClient.QueryOrgIdentityInfo(&esign.QueryOrgIdentityRequest{
|
response.Metadata = map[string]interface{}{
|
||||||
OrgName: cmd.CompanyName,
|
"next_action": "您已通过审核,请完成企业认证步骤",
|
||||||
})
|
|
||||||
if identity != nil && identity.Data.RealnameStatus == 1 {
|
|
||||||
// 已提交
|
|
||||||
err = cert.SubmitEnterpriseInfo(enterpriseInfo, "", "")
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("提交企业认证信息失败: %s", err.Error())
|
|
||||||
}
|
}
|
||||||
s.logger.Info("企业认证成功", zap.Any("identity", identity))
|
return nil
|
||||||
|
|
||||||
// 完成企业认证流程
|
|
||||||
err = s.completeEnterpriseVerification(txCtx, cert, cmd.UserID, cmd.CompanyName, cmd.LegalPersonName)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
}
|
||||||
|
|
||||||
respMeta = map[string]interface{}{
|
// 4. 提交企业信息进入人工审核(不调用 e签宝,不生成认证链接)
|
||||||
|
err = cert.SubmitEnterpriseInfoForReview(enterpriseInfo)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("提交企业信息失败: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
respMeta := map[string]interface{}{
|
||||||
"enterprise_info": enterpriseInfo,
|
"enterprise_info": enterpriseInfo,
|
||||||
"next_action": "企业已认证,可进行后续操作",
|
"next_action": "请等待管理员审核企业信息",
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if err != nil {
|
|
||||||
s.logger.Error("e签宝查询企业认证信息失败或未进行企业认证", zap.Error(err))
|
|
||||||
}
|
|
||||||
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 {
|
|
||||||
s.logger.Error("生成企业认证链接失败", zap.Error(err))
|
|
||||||
return fmt.Errorf("生成企业认证链接失败: %s", err.Error())
|
|
||||||
}
|
|
||||||
err = cert.SubmitEnterpriseInfo(enterpriseInfo, authURL.AuthShortURL, authURL.AuthFlowID)
|
|
||||||
if err != nil {
|
|
||||||
return fmt.Errorf("提交企业认证信息失败: %s", err.Error())
|
|
||||||
}
|
|
||||||
respMeta = map[string]interface{}{
|
|
||||||
"enterprise_info": enterpriseInfo,
|
|
||||||
"authUrl": authURL.AuthURL,
|
|
||||||
"next_action": "请完成企业认证",
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
err = s.aggregateService.SaveCertification(txCtx, cert)
|
err = s.aggregateService.SaveCertification(txCtx, cert)
|
||||||
@@ -694,6 +697,183 @@ func (s *CertificationApplicationServiceImpl) AdminCompleteCertificationWithoutC
|
|||||||
return response, nil
|
return response, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
||||||
|
func (s *CertificationApplicationServiceImpl) AdminListSubmitRecords(
|
||||||
|
ctx context.Context,
|
||||||
|
query *queries.AdminListSubmitRecordsQuery,
|
||||||
|
) (*responses.AdminSubmitRecordsListResponse, error) {
|
||||||
|
if query.PageSize <= 0 {
|
||||||
|
query.PageSize = 10
|
||||||
|
}
|
||||||
|
if query.Page <= 0 {
|
||||||
|
query.Page = 1
|
||||||
|
}
|
||||||
|
filter := repositories.ListSubmitRecordsFilter{
|
||||||
|
Page: query.Page,
|
||||||
|
PageSize: query.PageSize,
|
||||||
|
ManualReviewStatus: query.ManualReviewStatus,
|
||||||
|
}
|
||||||
|
result, err := s.enterpriseInfoSubmitRecordRepo.List(ctx, filter)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("查询提交记录失败: %w", err)
|
||||||
|
}
|
||||||
|
items := make([]*responses.AdminSubmitRecordItem, 0, len(result.Records))
|
||||||
|
for _, r := range result.Records {
|
||||||
|
certStatus := ""
|
||||||
|
if cert, err := s.aggregateService.LoadCertificationByUserID(ctx, r.UserID); err == nil && cert != nil {
|
||||||
|
certStatus = string(cert.Status)
|
||||||
|
}
|
||||||
|
items = append(items, &responses.AdminSubmitRecordItem{
|
||||||
|
ID: r.ID,
|
||||||
|
UserID: r.UserID,
|
||||||
|
CompanyName: r.CompanyName,
|
||||||
|
UnifiedSocialCode: r.UnifiedSocialCode,
|
||||||
|
LegalPersonName: r.LegalPersonName,
|
||||||
|
SubmitAt: r.SubmitAt,
|
||||||
|
Status: r.Status,
|
||||||
|
ManualReviewStatus: r.ManualReviewStatus,
|
||||||
|
ManualReviewedAt: r.ManualReviewedAt,
|
||||||
|
CertificationStatus: certStatus,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
totalPages := int((result.Total + int64(query.PageSize) - 1) / int64(query.PageSize))
|
||||||
|
if totalPages == 0 {
|
||||||
|
totalPages = 1
|
||||||
|
}
|
||||||
|
return &responses.AdminSubmitRecordsListResponse{
|
||||||
|
Items: items,
|
||||||
|
Total: result.Total,
|
||||||
|
Page: query.Page,
|
||||||
|
PageSize: query.PageSize,
|
||||||
|
TotalPages: totalPages,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
||||||
|
func (s *CertificationApplicationServiceImpl) AdminGetSubmitRecordByID(ctx context.Context, recordID string) (*responses.AdminSubmitRecordDetail, error) {
|
||||||
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("获取提交记录失败: %w", err)
|
||||||
|
}
|
||||||
|
certStatus := ""
|
||||||
|
if cert, loadErr := s.aggregateService.LoadCertificationByUserID(ctx, record.UserID); loadErr == nil && cert != nil {
|
||||||
|
certStatus = string(cert.Status)
|
||||||
|
}
|
||||||
|
return &responses.AdminSubmitRecordDetail{
|
||||||
|
ID: record.ID,
|
||||||
|
UserID: record.UserID,
|
||||||
|
CompanyName: record.CompanyName,
|
||||||
|
UnifiedSocialCode: record.UnifiedSocialCode,
|
||||||
|
LegalPersonName: record.LegalPersonName,
|
||||||
|
LegalPersonID: record.LegalPersonID,
|
||||||
|
LegalPersonPhone: record.LegalPersonPhone,
|
||||||
|
EnterpriseAddress: record.EnterpriseAddress,
|
||||||
|
AuthorizedRepName: record.AuthorizedRepName,
|
||||||
|
AuthorizedRepID: record.AuthorizedRepID,
|
||||||
|
AuthorizedRepPhone: record.AuthorizedRepPhone,
|
||||||
|
AuthorizedRepIDImageURLs: record.AuthorizedRepIDImageURLs,
|
||||||
|
BusinessLicenseImageURL: record.BusinessLicenseImageURL,
|
||||||
|
OfficePlaceImageURLs: record.OfficePlaceImageURLs,
|
||||||
|
APIUsage: record.APIUsage,
|
||||||
|
ScenarioAttachmentURLs: record.ScenarioAttachmentURLs,
|
||||||
|
Status: record.Status,
|
||||||
|
SubmitAt: record.SubmitAt,
|
||||||
|
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,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminApproveSubmitRecord 管理端审核通过
|
||||||
|
func (s *CertificationApplicationServiceImpl) AdminApproveSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
||||||
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
if err := s.aggregateService.SaveCertification(ctx, cert); err != nil {
|
||||||
|
return fmt.Errorf("保存认证信息失败: %w", err)
|
||||||
|
}
|
||||||
|
s.logger.Info("管理员审核通过企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminRejectSubmitRecord 管理端审核拒绝
|
||||||
|
func (s *CertificationApplicationServiceImpl) AdminRejectSubmitRecord(ctx context.Context, recordID, adminID, remark string) error {
|
||||||
|
if remark == "" {
|
||||||
|
return fmt.Errorf("拒绝时必须填写审核备注")
|
||||||
|
}
|
||||||
|
record, err := s.enterpriseInfoSubmitRecordRepo.FindByID(ctx, recordID)
|
||||||
|
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 {
|
||||||
|
return fmt.Errorf("认证状态不是待审核,当前: %s", enums.GetStatusName(cert.Status))
|
||||||
|
}
|
||||||
|
record.MarkManualRejected(adminID, remark)
|
||||||
|
if err := s.enterpriseInfoSubmitRecordService.Save(ctx, record); err != nil {
|
||||||
|
return fmt.Errorf("保存提交记录失败: %w", 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)
|
||||||
|
}
|
||||||
|
s.logger.Info("管理员审核拒绝企业信息", zap.String("record_id", recordID), zap.String("admin_id", adminID))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ================ 辅助方法 ================
|
// ================ 辅助方法 ================
|
||||||
|
|
||||||
// convertToResponse 转换实体为响应DTO
|
// convertToResponse 转换实体为响应DTO
|
||||||
|
|||||||
@@ -104,4 +104,19 @@ type SubmitEnterpriseInfoCommand struct {
|
|||||||
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号,11位,如:13800138000"`
|
LegalPersonPhone string `json:"legal_person_phone" binding:"required,phone" comment:"法定代表人手机号,11位,如:13800138000"`
|
||||||
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
|
EnterpriseAddress string `json:"enterprise_address" binding:"required,enterprise_address" comment:"企业地址,如:北京市海淀区"`
|
||||||
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
|
VerificationCode string `json:"verification_code" binding:"required,len=6" comment:"验证码"`
|
||||||
|
|
||||||
|
// 营业执照图片 URL(单张)
|
||||||
|
BusinessLicenseImageURL string `json:"business_license_image_url" binding:"omitempty,url" comment:"营业执照图片URL"`
|
||||||
|
// 办公场地图片 URL 列表(前端传 string 数组)
|
||||||
|
OfficePlaceImageURLs []string `json:"office_place_image_urls" binding:"omitempty,dive,url" comment:"办公场地图片URL列表"`
|
||||||
|
|
||||||
|
// 授权代表信息(与前端 authorized_rep_* 及表字段一致)
|
||||||
|
AuthorizedRepName string `json:"authorized_rep_name" binding:"omitempty,min=2,max=20" comment:"授权代表姓名"`
|
||||||
|
AuthorizedRepID string `json:"authorized_rep_id" binding:"omitempty,id_card" comment:"授权代表身份证号"`
|
||||||
|
AuthorizedRepPhone string `json:"authorized_rep_phone" binding:"omitempty,phone" comment:"授权代表手机号"`
|
||||||
|
AuthorizedRepIDImageURLs []string `json:"authorized_rep_id_image_urls" binding:"omitempty,dive,url" comment:"授权代表身份证正反面图片URL"`
|
||||||
|
|
||||||
|
// 应用场景
|
||||||
|
APIUsage string `json:"api_usage" binding:"omitempty,min=5,max=500" comment:"接口用途及业务场景说明"`
|
||||||
|
ScenarioAttachmentURLs []string `json:"scenario_attachment_urls" binding:"omitempty,dive,url" comment:"场景附件图片URL列表"`
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -192,3 +192,10 @@ func (q *GetSystemMonitoringQuery) ShouldIncludeMetric(metric string) bool {
|
|||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminListSubmitRecordsQuery 管理端企业信息提交记录列表查询
|
||||||
|
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,空为全部
|
||||||
|
}
|
||||||
|
|||||||
@@ -81,7 +81,6 @@ type ContractSignUrlResponse struct {
|
|||||||
Message string `json:"message"`
|
Message string `json:"message"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// SystemMonitoringResponse 系统监控响应
|
// SystemMonitoringResponse 系统监控响应
|
||||||
type SystemMonitoringResponse struct {
|
type SystemMonitoringResponse struct {
|
||||||
TimeRange string `json:"time_range"`
|
TimeRange string `json:"time_range"`
|
||||||
@@ -111,6 +110,61 @@ type SystemHealthStatus struct {
|
|||||||
Details map[string]interface{} `json:"details,omitempty"`
|
Details map[string]interface{} `json:"details,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminSubmitRecordItem 管理端提交记录列表项
|
||||||
|
type AdminSubmitRecordItem struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
UnifiedSocialCode string `json:"unified_social_code"`
|
||||||
|
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"` // 该用户当前认证状态,用于前端判断是否已完成企业认证并显示「已审核」
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminSubmitRecordDetail 管理端提交记录详情(含完整信息与图片 URL)
|
||||||
|
type AdminSubmitRecordDetail struct {
|
||||||
|
ID string `json:"id"`
|
||||||
|
UserID string `json:"user_id"`
|
||||||
|
CompanyName string `json:"company_name"`
|
||||||
|
UnifiedSocialCode string `json:"unified_social_code"`
|
||||||
|
LegalPersonName string `json:"legal_person_name"`
|
||||||
|
LegalPersonID string `json:"legal_person_id"`
|
||||||
|
LegalPersonPhone string `json:"legal_person_phone"`
|
||||||
|
EnterpriseAddress string `json:"enterprise_address"`
|
||||||
|
AuthorizedRepName string `json:"authorized_rep_name"`
|
||||||
|
AuthorizedRepID string `json:"authorized_rep_id"`
|
||||||
|
AuthorizedRepPhone string `json:"authorized_rep_phone"`
|
||||||
|
AuthorizedRepIDImageURLs string `json:"authorized_rep_id_image_urls"` // JSON 字符串或解析后数组
|
||||||
|
BusinessLicenseImageURL string `json:"business_license_image_url"`
|
||||||
|
OfficePlaceImageURLs string `json:"office_place_image_urls"` // JSON 数组字符串
|
||||||
|
APIUsage string `json:"api_usage"`
|
||||||
|
ScenarioAttachmentURLs string `json:"scenario_attachment_urls"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
SubmitAt time.Time `json:"submit_at"`
|
||||||
|
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"` // 该用户当前认证状态,用于前端显示「已审核」
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminSubmitRecordsListResponse 管理端提交记录列表响应
|
||||||
|
type AdminSubmitRecordsListResponse struct {
|
||||||
|
Items []*AdminSubmitRecordItem `json:"items"`
|
||||||
|
Total int64 `json:"total"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
PageSize int `json:"page_size"`
|
||||||
|
TotalPages int `json:"total_pages"`
|
||||||
|
}
|
||||||
|
|
||||||
// ================ 响应构建辅助方法 ================
|
// ================ 响应构建辅助方法 ================
|
||||||
|
|
||||||
// NewCertificationListResponse 创建认证列表响应
|
// NewCertificationListResponse 创建认证列表响应
|
||||||
@@ -146,7 +200,6 @@ func NewContractSignUrlResponse(certificationID, signURL, contractURL, nextActio
|
|||||||
return response
|
return response
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// NewSystemAlert 创建系统警告
|
// NewSystemAlert 创建系统警告
|
||||||
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
|
func NewSystemAlert(level, alertType, message, metric string, value, threshold interface{}) *SystemAlert {
|
||||||
return &SystemAlert{
|
return &SystemAlert{
|
||||||
@@ -161,7 +214,6 @@ func NewSystemAlert(level, alertType, message, metric string, value, threshold i
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// IsHealthy 检查系统是否健康
|
// IsHealthy 检查系统是否健康
|
||||||
func (r *SystemMonitoringResponse) IsHealthy() bool {
|
func (r *SystemMonitoringResponse) IsHealthy() bool {
|
||||||
return r.SystemHealth.Overall == "healthy"
|
return r.SystemHealth.Overall == "healthy"
|
||||||
|
|||||||
@@ -153,7 +153,31 @@ func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, act
|
|||||||
|
|
||||||
// ================ 业务操作方法 ================
|
// ================ 业务操作方法 ================
|
||||||
|
|
||||||
// SubmitEnterpriseInfo 提交企业信息
|
// SubmitEnterpriseInfoForReview 提交企业信息进入人工审核(不调用 e签宝,不生成认证链接)
|
||||||
|
func (c *Certification) SubmitEnterpriseInfoForReview(enterpriseInfo *value_objects.EnterpriseInfo) error {
|
||||||
|
// 已处于待审核:幂等,直接成功
|
||||||
|
if c.Status == enums.StatusInfoPendingReview {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
|
||||||
|
return fmt.Errorf("当前状态 %s 不允许提交企业信息", enums.GetStatusName(c.Status))
|
||||||
|
}
|
||||||
|
if err := enterpriseInfo.Validate(); err != nil {
|
||||||
|
return fmt.Errorf("企业信息验证失败: %w", err)
|
||||||
|
}
|
||||||
|
if err := c.TransitionTo(enums.StatusInfoPendingReview, enums.ActorTypeUser, c.UserID, "用户提交企业信息,等待人工审核"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
c.addDomainEvent(&EnterpriseInfoSubmittedEvent{
|
||||||
|
CertificationID: c.ID,
|
||||||
|
UserID: c.UserID,
|
||||||
|
EnterpriseInfo: enterpriseInfo,
|
||||||
|
SubmittedAt: time.Now(),
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubmitEnterpriseInfo 提交企业信息(直接进入已提交,含认证链接;用于无审核或管理员审核通过后补链)
|
||||||
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo, authURL string, authFlowID string) error {
|
func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.EnterpriseInfo, authURL string, authFlowID string) error {
|
||||||
// 验证当前状态
|
// 验证当前状态
|
||||||
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
|
if c.Status != enums.StatusPending && c.Status != enums.StatusInfoRejected {
|
||||||
@@ -186,6 +210,33 @@ func (c *Certification) SubmitEnterpriseInfo(enterpriseInfo *value_objects.Enter
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ApproveEnterpriseInfoReview 管理员审核通过:从待审核转为已提交,并写入企业认证链接
|
||||||
|
func (c *Certification) ApproveEnterpriseInfoReview(authURL, authFlowID string, actorID string) error {
|
||||||
|
if c.Status != enums.StatusInfoPendingReview {
|
||||||
|
return fmt.Errorf("当前状态 %s 不允许执行审核通过", enums.GetStatusName(c.Status))
|
||||||
|
}
|
||||||
|
c.AuthURL = authURL
|
||||||
|
c.AuthFlowID = authFlowID
|
||||||
|
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeAdmin, actorID, "管理员审核通过"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
now := time.Now()
|
||||||
|
c.InfoSubmittedAt = &now
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RejectEnterpriseInfoReview 管理员审核拒绝
|
||||||
|
func (c *Certification) RejectEnterpriseInfoReview(actorID, message string) error {
|
||||||
|
if c.Status != enums.StatusInfoPendingReview {
|
||||||
|
return fmt.Errorf("当前状态 %s 不允许执行审核拒绝", enums.GetStatusName(c.Status))
|
||||||
|
}
|
||||||
|
c.setFailureInfo(enums.FailureReasonManualReviewRejected, message)
|
||||||
|
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeAdmin, actorID, "管理员审核拒绝"); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 完成企业认证
|
// 完成企业认证
|
||||||
func (c *Certification) CompleteEnterpriseVerification() error {
|
func (c *Certification) CompleteEnterpriseVerification() error {
|
||||||
if c.Status != enums.StatusInfoSubmitted {
|
if c.Status != enums.StatusInfoSubmitted {
|
||||||
@@ -448,6 +499,8 @@ func (c *Certification) CompleteCertification() error {
|
|||||||
func (c *Certification) GetDataByStatus() map[string]interface{} {
|
func (c *Certification) GetDataByStatus() map[string]interface{} {
|
||||||
data := map[string]interface{}{}
|
data := map[string]interface{}{}
|
||||||
switch c.Status {
|
switch c.Status {
|
||||||
|
case enums.StatusInfoPendingReview:
|
||||||
|
// 待审核,无额外数据
|
||||||
case enums.StatusInfoSubmitted:
|
case enums.StatusInfoSubmitted:
|
||||||
data["auth_url"] = c.AuthURL
|
data["auth_url"] = c.AuthURL
|
||||||
case enums.StatusInfoRejected:
|
case enums.StatusInfoRejected:
|
||||||
@@ -494,6 +547,8 @@ func (c *Certification) GetAvailableActions() []string {
|
|||||||
switch c.Status {
|
switch c.Status {
|
||||||
case enums.StatusPending:
|
case enums.StatusPending:
|
||||||
actions = append(actions, "submit_enterprise_info")
|
actions = append(actions, "submit_enterprise_info")
|
||||||
|
case enums.StatusInfoPendingReview:
|
||||||
|
// 等待人工审核,无用户操作
|
||||||
case enums.StatusEnterpriseVerified:
|
case enums.StatusEnterpriseVerified:
|
||||||
actions = append(actions, "apply_contract")
|
actions = append(actions, "apply_contract")
|
||||||
case enums.StatusInfoRejected, enums.StatusContractRejected, enums.StatusContractExpired:
|
case enums.StatusInfoRejected, enums.StatusContractRejected, enums.StatusContractExpired:
|
||||||
@@ -587,8 +642,9 @@ func (c *Certification) ValidateBusinessRules() error {
|
|||||||
|
|
||||||
// validateActorPermission 验证操作者权限
|
// validateActorPermission 验证操作者权限
|
||||||
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
|
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
|
||||||
// 定义状态转换的权限规则
|
// 定义状态转换的权限规则(目标状态 -> 允许的操作者)
|
||||||
permissions := map[enums.CertificationStatus][]enums.ActorType{
|
permissions := map[enums.CertificationStatus][]enums.ActorType{
|
||||||
|
enums.StatusInfoPendingReview: {enums.ActorTypeUser},
|
||||||
enums.StatusInfoSubmitted: {enums.ActorTypeUser, enums.ActorTypeAdmin},
|
enums.StatusInfoSubmitted: {enums.ActorTypeUser, enums.ActorTypeAdmin},
|
||||||
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||||
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||||
|
|||||||
@@ -19,6 +19,21 @@ type EnterpriseInfoSubmitRecord struct {
|
|||||||
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
|
LegalPersonID string `json:"legal_person_id" gorm:"type:varchar(50);not null"`
|
||||||
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
|
LegalPersonPhone string `json:"legal_person_phone" gorm:"type:varchar(50);not null"`
|
||||||
EnterpriseAddress string `json:"enterprise_address" gorm:"type:varchar(200);not null"` // 新增企业地址
|
EnterpriseAddress string `json:"enterprise_address" gorm:"type:varchar(200);not null"` // 新增企业地址
|
||||||
|
|
||||||
|
// 授权代表信息(gorm 指定列名,确保与表 enterprise_info_submit_records 列一致并正确读入)
|
||||||
|
AuthorizedRepName string `json:"authorized_rep_name" gorm:"column:authorized_rep_name;type:varchar(50);comment:授权代表姓名"`
|
||||||
|
AuthorizedRepID string `json:"authorized_rep_id" gorm:"column:authorized_rep_id;type:varchar(50);comment:授权代表身份证号"`
|
||||||
|
AuthorizedRepPhone string `json:"authorized_rep_phone" gorm:"column:authorized_rep_phone;type:varchar(50);comment:授权代表手机号"`
|
||||||
|
// 授权代表身份证正反面图片URL列表(JSON字符串),按顺序存储[人像面, 国徽面]
|
||||||
|
AuthorizedRepIDImageURLs string `json:"authorized_rep_id_image_urls" gorm:"column:authorized_rep_id_image_urls;type:text;comment:授权代表身份证正反面图片URL列表(JSON字符串)"`
|
||||||
|
|
||||||
|
// 企业资质与场地材料
|
||||||
|
BusinessLicenseImageURL string `json:"business_license_image_url" gorm:"type:varchar(500);comment:营业执照图片URL"`
|
||||||
|
OfficePlaceImageURLs string `json:"office_place_image_urls" gorm:"type:text;comment:办公场地图片URL列表(JSON字符串)"`
|
||||||
|
// 应用场景
|
||||||
|
APIUsage string `json:"api_usage" gorm:"type:text;comment:接口用途及业务场景说明"`
|
||||||
|
ScenarioAttachmentURLs string `json:"scenario_attachment_urls" gorm:"type:text;comment:场景附件图片URL列表(JSON字符串)"`
|
||||||
|
|
||||||
// 提交状态
|
// 提交状态
|
||||||
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
|
Status string `json:"status" gorm:"type:varchar(20);not null;default:'submitted'"` // submitted, verified, failed
|
||||||
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
||||||
@@ -26,6 +41,12 @@ type EnterpriseInfoSubmitRecord struct {
|
|||||||
FailedAt *time.Time `json:"failed_at"`
|
FailedAt *time.Time `json:"failed_at"`
|
||||||
FailureReason string `json:"failure_reason" gorm:"type:text"`
|
FailureReason string `json:"failure_reason" gorm:"type:text"`
|
||||||
|
|
||||||
|
// 人工审核信息
|
||||||
|
ManualReviewStatus string `json:"manual_review_status" gorm:"type:varchar(20);not null;default:'pending';comment:人工审核状态(pending,approved,rejected)"`
|
||||||
|
ManualReviewRemark string `json:"manual_review_remark" gorm:"type:text;comment:人工审核备注"`
|
||||||
|
ManualReviewedAt *time.Time `json:"manual_reviewed_at" gorm:"comment:人工审核时间"`
|
||||||
|
ManualReviewerID string `json:"manual_reviewer_id" gorm:"type:varchar(36);comment:人工审核人ID"`
|
||||||
|
|
||||||
// 系统字段
|
// 系统字段
|
||||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||||
@@ -51,6 +72,7 @@ func NewEnterpriseInfoSubmitRecord(
|
|||||||
LegalPersonPhone: legalPersonPhone,
|
LegalPersonPhone: legalPersonPhone,
|
||||||
EnterpriseAddress: enterpriseAddress,
|
EnterpriseAddress: enterpriseAddress,
|
||||||
Status: "submitted",
|
Status: "submitted",
|
||||||
|
ManualReviewStatus: "pending",
|
||||||
SubmitAt: time.Now(),
|
SubmitAt: time.Now(),
|
||||||
CreatedAt: time.Now(),
|
CreatedAt: time.Now(),
|
||||||
UpdatedAt: time.Now(),
|
UpdatedAt: time.Now(),
|
||||||
@@ -74,6 +96,26 @@ func (r *EnterpriseInfoSubmitRecord) MarkAsFailed(reason string) {
|
|||||||
r.UpdatedAt = now
|
r.UpdatedAt = now
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// MarkManualApproved 标记人工审核通过
|
||||||
|
func (r *EnterpriseInfoSubmitRecord) MarkManualApproved(reviewerID, remark string) {
|
||||||
|
now := time.Now()
|
||||||
|
r.ManualReviewStatus = "approved"
|
||||||
|
r.ManualReviewedAt = &now
|
||||||
|
r.ManualReviewerID = reviewerID
|
||||||
|
r.ManualReviewRemark = remark
|
||||||
|
r.UpdatedAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarkManualRejected 标记人工审核拒绝
|
||||||
|
func (r *EnterpriseInfoSubmitRecord) MarkManualRejected(reviewerID, remark string) {
|
||||||
|
now := time.Now()
|
||||||
|
r.ManualReviewStatus = "rejected"
|
||||||
|
r.ManualReviewedAt = &now
|
||||||
|
r.ManualReviewerID = reviewerID
|
||||||
|
r.ManualReviewRemark = remark
|
||||||
|
r.UpdatedAt = now
|
||||||
|
}
|
||||||
|
|
||||||
// IsVerified 检查是否已验证
|
// IsVerified 检查是否已验证
|
||||||
func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
|
func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
|
||||||
return r.Status == "verified"
|
return r.Status == "verified"
|
||||||
|
|||||||
@@ -6,7 +6,8 @@ type CertificationStatus string
|
|||||||
const (
|
const (
|
||||||
// === 主流程状态 ===
|
// === 主流程状态 ===
|
||||||
StatusPending CertificationStatus = "pending" // 待认证
|
StatusPending CertificationStatus = "pending" // 待认证
|
||||||
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息
|
StatusInfoPendingReview CertificationStatus = "info_pending_review" // 企业信息待人工审核
|
||||||
|
StatusInfoSubmitted CertificationStatus = "info_submitted" // 已提交企业信息(审核通过)
|
||||||
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
|
StatusEnterpriseVerified CertificationStatus = "enterprise_verified" // 已企业认证
|
||||||
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
|
StatusContractApplied CertificationStatus = "contract_applied" // 已申请签署合同
|
||||||
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
|
StatusContractSigned CertificationStatus = "contract_signed" // 已签署合同
|
||||||
@@ -21,6 +22,7 @@ const (
|
|||||||
// AllStatuses 所有有效状态列表
|
// AllStatuses 所有有效状态列表
|
||||||
var AllStatuses = []CertificationStatus{
|
var AllStatuses = []CertificationStatus{
|
||||||
StatusPending,
|
StatusPending,
|
||||||
|
StatusInfoPendingReview,
|
||||||
StatusInfoSubmitted,
|
StatusInfoSubmitted,
|
||||||
StatusEnterpriseVerified,
|
StatusEnterpriseVerified,
|
||||||
StatusContractApplied,
|
StatusContractApplied,
|
||||||
@@ -34,6 +36,7 @@ var AllStatuses = []CertificationStatus{
|
|||||||
// MainFlowStatuses 主流程状态列表
|
// MainFlowStatuses 主流程状态列表
|
||||||
var MainFlowStatuses = []CertificationStatus{
|
var MainFlowStatuses = []CertificationStatus{
|
||||||
StatusPending,
|
StatusPending,
|
||||||
|
StatusInfoPendingReview,
|
||||||
StatusInfoSubmitted,
|
StatusInfoSubmitted,
|
||||||
StatusEnterpriseVerified,
|
StatusEnterpriseVerified,
|
||||||
StatusContractApplied,
|
StatusContractApplied,
|
||||||
@@ -61,6 +64,7 @@ func IsValidStatus(status CertificationStatus) bool {
|
|||||||
func GetStatusName(status CertificationStatus) string {
|
func GetStatusName(status CertificationStatus) string {
|
||||||
statusNames := map[CertificationStatus]string{
|
statusNames := map[CertificationStatus]string{
|
||||||
StatusPending: "待认证",
|
StatusPending: "待认证",
|
||||||
|
StatusInfoPendingReview: "企业信息待审核",
|
||||||
StatusInfoSubmitted: "已提交企业信息",
|
StatusInfoSubmitted: "已提交企业信息",
|
||||||
StatusEnterpriseVerified: "已企业认证",
|
StatusEnterpriseVerified: "已企业认证",
|
||||||
StatusContractApplied: "已申请签署合同",
|
StatusContractApplied: "已申请签署合同",
|
||||||
@@ -120,14 +124,15 @@ func GetStatusCategory(status CertificationStatus) string {
|
|||||||
func GetStatusPriority(status CertificationStatus) int {
|
func GetStatusPriority(status CertificationStatus) int {
|
||||||
priorities := map[CertificationStatus]int{
|
priorities := map[CertificationStatus]int{
|
||||||
StatusPending: 1,
|
StatusPending: 1,
|
||||||
StatusInfoSubmitted: 2,
|
StatusInfoPendingReview: 2,
|
||||||
StatusEnterpriseVerified: 3,
|
StatusInfoSubmitted: 3,
|
||||||
StatusContractApplied: 4,
|
StatusEnterpriseVerified: 4,
|
||||||
StatusContractSigned: 5,
|
StatusContractApplied: 5,
|
||||||
StatusCompleted: 6,
|
StatusContractSigned: 6,
|
||||||
StatusInfoRejected: 7,
|
StatusCompleted: 7,
|
||||||
StatusContractRejected: 8,
|
StatusInfoRejected: 8,
|
||||||
StatusContractExpired: 9,
|
StatusContractRejected: 9,
|
||||||
|
StatusContractExpired: 10,
|
||||||
}
|
}
|
||||||
|
|
||||||
if priority, exists := priorities[status]; exists {
|
if priority, exists := priorities[status]; exists {
|
||||||
@@ -140,6 +145,7 @@ func GetStatusPriority(status CertificationStatus) int {
|
|||||||
func GetProgressPercentage(status CertificationStatus) int {
|
func GetProgressPercentage(status CertificationStatus) int {
|
||||||
progressMap := map[CertificationStatus]int{
|
progressMap := map[CertificationStatus]int{
|
||||||
StatusPending: 0,
|
StatusPending: 0,
|
||||||
|
StatusInfoPendingReview: 15,
|
||||||
StatusInfoSubmitted: 25,
|
StatusInfoSubmitted: 25,
|
||||||
StatusEnterpriseVerified: 50,
|
StatusEnterpriseVerified: 50,
|
||||||
StatusContractApplied: 75,
|
StatusContractApplied: 75,
|
||||||
@@ -160,7 +166,8 @@ func GetProgressPercentage(status CertificationStatus) int {
|
|||||||
func IsUserActionRequired(status CertificationStatus) bool {
|
func IsUserActionRequired(status CertificationStatus) bool {
|
||||||
userActionRequired := map[CertificationStatus]bool{
|
userActionRequired := map[CertificationStatus]bool{
|
||||||
StatusPending: true, // 需要提交企业信息
|
StatusPending: true, // 需要提交企业信息
|
||||||
StatusInfoSubmitted: false, // 等待系统验证
|
StatusInfoPendingReview: false, // 等待人工审核
|
||||||
|
StatusInfoSubmitted: false, // 等待完成企业认证
|
||||||
StatusEnterpriseVerified: true, // 需要申请合同
|
StatusEnterpriseVerified: true, // 需要申请合同
|
||||||
StatusContractApplied: true, // 需要签署合同
|
StatusContractApplied: true, // 需要签署合同
|
||||||
StatusContractSigned: false, // 合同已签署,等待系统处理
|
StatusContractSigned: false, // 合同已签署,等待系统处理
|
||||||
@@ -180,6 +187,7 @@ func IsUserActionRequired(status CertificationStatus) bool {
|
|||||||
func GetUserActionHint(status CertificationStatus) string {
|
func GetUserActionHint(status CertificationStatus) string {
|
||||||
hints := map[CertificationStatus]string{
|
hints := map[CertificationStatus]string{
|
||||||
StatusPending: "请提交企业信息",
|
StatusPending: "请提交企业信息",
|
||||||
|
StatusInfoPendingReview: "企业信息已提交,请等待管理员审核",
|
||||||
StatusInfoSubmitted: "请完成企业认证",
|
StatusInfoSubmitted: "请完成企业认证",
|
||||||
StatusEnterpriseVerified: "企业认证完成,请申请签署合同",
|
StatusEnterpriseVerified: "企业认证完成,请申请签署合同",
|
||||||
StatusContractApplied: "请在规定时间内完成合同签署",
|
StatusContractApplied: "请在规定时间内完成合同签署",
|
||||||
@@ -200,8 +208,12 @@ func GetUserActionHint(status CertificationStatus) string {
|
|||||||
func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStatus {
|
func GetNextValidStatuses(currentStatus CertificationStatus) []CertificationStatus {
|
||||||
nextStatusMap := map[CertificationStatus][]CertificationStatus{
|
nextStatusMap := map[CertificationStatus][]CertificationStatus{
|
||||||
StatusPending: {
|
StatusPending: {
|
||||||
|
StatusInfoPendingReview, // 用户提交企业信息,进入待审核
|
||||||
|
StatusCompleted,
|
||||||
|
},
|
||||||
|
StatusInfoPendingReview: {
|
||||||
StatusInfoSubmitted,
|
StatusInfoSubmitted,
|
||||||
// 管理员/系统可直接完成认证
|
StatusInfoRejected,
|
||||||
StatusCompleted,
|
StatusCompleted,
|
||||||
},
|
},
|
||||||
StatusInfoSubmitted: {
|
StatusInfoSubmitted: {
|
||||||
@@ -265,6 +277,9 @@ func CanTransitionTo(currentStatus, targetStatus CertificationStatus) bool {
|
|||||||
// GetTransitionReason 获取状态转换的原因描述
|
// GetTransitionReason 获取状态转换的原因描述
|
||||||
func GetTransitionReason(from, to CertificationStatus) string {
|
func GetTransitionReason(from, to CertificationStatus) string {
|
||||||
transitionReasons := map[string]string{
|
transitionReasons := map[string]string{
|
||||||
|
string(StatusPending) + "->" + string(StatusInfoPendingReview): "用户提交企业信息,等待人工审核",
|
||||||
|
string(StatusInfoPendingReview) + "->" + string(StatusInfoSubmitted): "管理员审核通过",
|
||||||
|
string(StatusInfoPendingReview) + "->" + string(StatusInfoRejected): "管理员审核拒绝",
|
||||||
string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息",
|
string(StatusPending) + "->" + string(StatusInfoSubmitted): "用户提交企业信息",
|
||||||
string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功",
|
string(StatusInfoSubmitted) + "->" + string(StatusEnterpriseVerified): "e签宝企业认证成功",
|
||||||
string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败",
|
string(StatusInfoSubmitted) + "->" + string(StatusInfoRejected): "e签宝企业认证失败",
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ const (
|
|||||||
FailureReasonLegalPersonMismatch FailureReason = "legal_person_mismatch" // 法定代表人信息不匹配
|
FailureReasonLegalPersonMismatch FailureReason = "legal_person_mismatch" // 法定代表人信息不匹配
|
||||||
FailureReasonEsignVerificationFailed FailureReason = "esign_verification_failed" // e签宝验证失败
|
FailureReasonEsignVerificationFailed FailureReason = "esign_verification_failed" // e签宝验证失败
|
||||||
FailureReasonInvalidDocument FailureReason = "invalid_document" // 证件信息无效
|
FailureReasonInvalidDocument FailureReason = "invalid_document" // 证件信息无效
|
||||||
|
FailureReasonManualReviewRejected FailureReason = "manual_review_rejected" // 人工审核拒绝
|
||||||
|
|
||||||
// === 合同签署失败原因 ===
|
// === 合同签署失败原因 ===
|
||||||
FailureReasonContractRejectedByUser FailureReason = "contract_rejected_by_user" // 用户拒绝签署
|
FailureReasonContractRejectedByUser FailureReason = "contract_rejected_by_user" // 用户拒绝签署
|
||||||
@@ -35,7 +36,7 @@ var AllFailureReasons = []FailureReason{
|
|||||||
FailureReasonLegalPersonMismatch,
|
FailureReasonLegalPersonMismatch,
|
||||||
FailureReasonEsignVerificationFailed,
|
FailureReasonEsignVerificationFailed,
|
||||||
FailureReasonInvalidDocument,
|
FailureReasonInvalidDocument,
|
||||||
|
FailureReasonManualReviewRejected,
|
||||||
// 合同签署失败
|
// 合同签署失败
|
||||||
FailureReasonContractRejectedByUser,
|
FailureReasonContractRejectedByUser,
|
||||||
FailureReasonContractExpired,
|
FailureReasonContractExpired,
|
||||||
@@ -97,7 +98,7 @@ func GetFailureReasonName(reason FailureReason) string {
|
|||||||
FailureReasonLegalPersonMismatch: "法定代表人信息不匹配",
|
FailureReasonLegalPersonMismatch: "法定代表人信息不匹配",
|
||||||
FailureReasonEsignVerificationFailed: "e签宝验证失败",
|
FailureReasonEsignVerificationFailed: "e签宝验证失败",
|
||||||
FailureReasonInvalidDocument: "证件信息无效",
|
FailureReasonInvalidDocument: "证件信息无效",
|
||||||
|
FailureReasonManualReviewRejected: "人工审核拒绝",
|
||||||
// 合同签署失败
|
// 合同签署失败
|
||||||
FailureReasonContractRejectedByUser: "用户拒绝签署",
|
FailureReasonContractRejectedByUser: "用户拒绝签署",
|
||||||
FailureReasonContractExpired: "合同签署超时",
|
FailureReasonContractExpired: "合同签署超时",
|
||||||
@@ -128,7 +129,7 @@ func GetFailureReasonCategory(reason FailureReason) string {
|
|||||||
FailureReasonLegalPersonMismatch: "企业验证",
|
FailureReasonLegalPersonMismatch: "企业验证",
|
||||||
FailureReasonEsignVerificationFailed: "企业验证",
|
FailureReasonEsignVerificationFailed: "企业验证",
|
||||||
FailureReasonInvalidDocument: "企业验证",
|
FailureReasonInvalidDocument: "企业验证",
|
||||||
|
FailureReasonManualReviewRejected: "人工审核",
|
||||||
// 合同签署失败
|
// 合同签署失败
|
||||||
FailureReasonContractRejectedByUser: "合同签署",
|
FailureReasonContractRejectedByUser: "合同签署",
|
||||||
FailureReasonContractExpired: "合同签署",
|
FailureReasonContractExpired: "合同签署",
|
||||||
@@ -189,7 +190,7 @@ func GetSuggestedAction(reason FailureReason) string {
|
|||||||
FailureReasonLegalPersonMismatch: "请核对法定代表人信息是否正确",
|
FailureReasonLegalPersonMismatch: "请核对法定代表人信息是否正确",
|
||||||
FailureReasonEsignVerificationFailed: "请稍后重试,如持续失败请联系客服",
|
FailureReasonEsignVerificationFailed: "请稍后重试,如持续失败请联系客服",
|
||||||
FailureReasonInvalidDocument: "请检查证件信息是否有效",
|
FailureReasonInvalidDocument: "请检查证件信息是否有效",
|
||||||
|
FailureReasonManualReviewRejected: "请根据审核意见修正后重新提交,或联系客服",
|
||||||
// 合同签署失败
|
// 合同签署失败
|
||||||
FailureReasonContractRejectedByUser: "您可以重新申请签署合同",
|
FailureReasonContractRejectedByUser: "您可以重新申请签署合同",
|
||||||
FailureReasonContractExpired: "请重新申请签署合同",
|
FailureReasonContractExpired: "请重新申请签署合同",
|
||||||
@@ -220,7 +221,7 @@ func IsRetryable(reason FailureReason) bool {
|
|||||||
FailureReasonLegalPersonMismatch: true,
|
FailureReasonLegalPersonMismatch: true,
|
||||||
FailureReasonEsignVerificationFailed: true, // 可能是临时问题
|
FailureReasonEsignVerificationFailed: true, // 可能是临时问题
|
||||||
FailureReasonInvalidDocument: true,
|
FailureReasonInvalidDocument: true,
|
||||||
|
FailureReasonManualReviewRejected: true, // 用户可修正后重新提交
|
||||||
// 合同签署失败
|
// 合同签署失败
|
||||||
FailureReasonContractRejectedByUser: true, // 用户可以改变主意
|
FailureReasonContractRejectedByUser: true, // 用户可以改变主意
|
||||||
FailureReasonContractExpired: true, // 可以重新申请
|
FailureReasonContractExpired: true, // 可以重新申请
|
||||||
@@ -253,6 +254,7 @@ func GetRetrySuggestion(reason FailureReason) string {
|
|||||||
FailureReasonLegalPersonMismatch: "请确认法定代表人信息后重新提交",
|
FailureReasonLegalPersonMismatch: "请确认法定代表人信息后重新提交",
|
||||||
FailureReasonEsignVerificationFailed: "请稍后重新尝试",
|
FailureReasonEsignVerificationFailed: "请稍后重新尝试",
|
||||||
FailureReasonInvalidDocument: "请检查证件信息后重新提交",
|
FailureReasonInvalidDocument: "请检查证件信息后重新提交",
|
||||||
|
FailureReasonManualReviewRejected: "请根据审核意见修正企业信息后重新提交",
|
||||||
FailureReasonContractRejectedByUser: "如需要可重新申请合同",
|
FailureReasonContractRejectedByUser: "如需要可重新申请合同",
|
||||||
FailureReasonContractExpired: "请重新申请合同签署",
|
FailureReasonContractExpired: "请重新申请合同签署",
|
||||||
FailureReasonSignProcessFailed: "请重新尝试签署",
|
FailureReasonSignProcessFailed: "请重新尝试签署",
|
||||||
|
|||||||
@@ -5,10 +5,25 @@ import (
|
|||||||
"tyapi-server/internal/domains/certification/entities"
|
"tyapi-server/internal/domains/certification/entities"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// ListSubmitRecordsFilter 提交记录列表筛选
|
||||||
|
type ListSubmitRecordsFilter struct {
|
||||||
|
ManualReviewStatus string // pending, approved, rejected,空表示全部
|
||||||
|
Page int
|
||||||
|
PageSize int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ListSubmitRecordsResult 列表结果
|
||||||
|
type ListSubmitRecordsResult struct {
|
||||||
|
Records []*entities.EnterpriseInfoSubmitRecord
|
||||||
|
Total int64
|
||||||
|
}
|
||||||
|
|
||||||
type EnterpriseInfoSubmitRecordRepository interface {
|
type EnterpriseInfoSubmitRecordRepository interface {
|
||||||
Create(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
|
Create(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
|
||||||
Update(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
|
Update(ctx context.Context, record *entities.EnterpriseInfoSubmitRecord) error
|
||||||
Exists(ctx context.Context, ID string) (bool, error)
|
Exists(ctx context.Context, ID string) (bool, error)
|
||||||
|
FindByID(ctx context.Context, id string) (*entities.EnterpriseInfoSubmitRecord, error)
|
||||||
FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
||||||
FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
FindLatestVerifiedByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error)
|
||||||
|
List(ctx context.Context, filter ListSubmitRecordsFilter) (*ListSubmitRecordsResult, error)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package certification
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"tyapi-server/internal/domains/certification/entities"
|
"tyapi-server/internal/domains/certification/entities"
|
||||||
|
"tyapi-server/internal/domains/certification/repositories"
|
||||||
"tyapi-server/internal/shared/database"
|
"tyapi-server/internal/shared/database"
|
||||||
|
|
||||||
"go.uber.org/zap"
|
"go.uber.org/zap"
|
||||||
@@ -39,6 +40,15 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) Exists(ctx context.Context, I
|
|||||||
return r.ExistsEntity(ctx, ID, &entities.EnterpriseInfoSubmitRecord{})
|
return r.ExistsEntity(ctx, ID, &entities.EnterpriseInfoSubmitRecord{})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *GormEnterpriseInfoSubmitRecordRepository) FindByID(ctx context.Context, id string) (*entities.EnterpriseInfoSubmitRecord, error) {
|
||||||
|
var record entities.EnterpriseInfoSubmitRecord
|
||||||
|
err := r.GetDB(ctx).Where("id = ?", id).First(&record).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &record, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (r *GormEnterpriseInfoSubmitRecordRepository) FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
|
func (r *GormEnterpriseInfoSubmitRecordRepository) FindLatestByUserID(ctx context.Context, userID string) (*entities.EnterpriseInfoSubmitRecord, error) {
|
||||||
var record entities.EnterpriseInfoSubmitRecord
|
var record entities.EnterpriseInfoSubmitRecord
|
||||||
err := r.GetDB(ctx).
|
err := r.GetDB(ctx).
|
||||||
@@ -62,3 +72,31 @@ func (r *GormEnterpriseInfoSubmitRecordRepository) FindLatestVerifiedByUserID(ct
|
|||||||
}
|
}
|
||||||
return &record, nil
|
return &record, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
var total int64
|
||||||
|
if err := db.Count(&total).Error; err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
if filter.PageSize <= 0 {
|
||||||
|
filter.PageSize = 10
|
||||||
|
}
|
||||||
|
if filter.Page <= 0 {
|
||||||
|
filter.Page = 1
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
err := q.Order("submit_at DESC").Offset(offset).Limit(filter.PageSize).Find(&records).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &repositories.ListSubmitRecordsResult{Records: records, Total: total}, nil
|
||||||
|
}
|
||||||
@@ -14,6 +14,7 @@ import (
|
|||||||
"tyapi-server/internal/application/certification/dto/commands"
|
"tyapi-server/internal/application/certification/dto/commands"
|
||||||
"tyapi-server/internal/application/certification/dto/queries"
|
"tyapi-server/internal/application/certification/dto/queries"
|
||||||
_ "tyapi-server/internal/application/certification/dto/responses"
|
_ "tyapi-server/internal/application/certification/dto/responses"
|
||||||
|
"tyapi-server/internal/infrastructure/external/storage"
|
||||||
"tyapi-server/internal/shared/interfaces"
|
"tyapi-server/internal/shared/interfaces"
|
||||||
"tyapi-server/internal/shared/middleware"
|
"tyapi-server/internal/shared/middleware"
|
||||||
)
|
)
|
||||||
@@ -25,6 +26,7 @@ type CertificationHandler struct {
|
|||||||
validator interfaces.RequestValidator
|
validator interfaces.RequestValidator
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
jwtAuth *middleware.JWTAuthMiddleware
|
jwtAuth *middleware.JWTAuthMiddleware
|
||||||
|
storageService *storage.QiNiuStorageService
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewCertificationHandler 创建认证处理器
|
// NewCertificationHandler 创建认证处理器
|
||||||
@@ -34,6 +36,7 @@ func NewCertificationHandler(
|
|||||||
validator interfaces.RequestValidator,
|
validator interfaces.RequestValidator,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
jwtAuth *middleware.JWTAuthMiddleware,
|
jwtAuth *middleware.JWTAuthMiddleware,
|
||||||
|
storageService *storage.QiNiuStorageService,
|
||||||
) *CertificationHandler {
|
) *CertificationHandler {
|
||||||
return &CertificationHandler{
|
return &CertificationHandler{
|
||||||
appService: appService,
|
appService: appService,
|
||||||
@@ -41,6 +44,7 @@ func NewCertificationHandler(
|
|||||||
validator: validator,
|
validator: validator,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
jwtAuth: jwtAuth,
|
jwtAuth: jwtAuth,
|
||||||
|
storageService: storageService,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -295,6 +299,78 @@ func (h *CertificationHandler) RecognizeBusinessLicense(c *gin.Context) {
|
|||||||
h.response.Success(c, result, "营业执照识别成功")
|
h.response.Success(c, result, "营业执照识别成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadCertificationFile 上传认证相关图片到七牛云(企业信息中的营业执照、办公场地、场景附件、授权代表身份证等)
|
||||||
|
// @Summary 上传认证图片
|
||||||
|
// @Description 上传企业信息中使用的图片到七牛云,返回可访问的 URL
|
||||||
|
// @Tags 认证管理
|
||||||
|
// @Accept multipart/form-data
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param file formData file true "图片文件"
|
||||||
|
// @Success 200 {object} map[string]string "上传成功,返回 url 与 key"
|
||||||
|
// @Failure 400 {object} map[string]interface{} "请求参数错误"
|
||||||
|
// @Failure 401 {object} map[string]interface{} "未认证"
|
||||||
|
// @Failure 500 {object} map[string]interface{} "服务器内部错误"
|
||||||
|
// @Router /api/v1/certifications/upload [post]
|
||||||
|
func (h *CertificationHandler) UploadCertificationFile(c *gin.Context) {
|
||||||
|
userID := h.getCurrentUserID(c)
|
||||||
|
if userID == "" {
|
||||||
|
h.response.Unauthorized(c, "用户未登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
file, err := c.FormFile("file")
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("获取上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
|
h.response.BadRequest(c, "请选择要上传的图片文件")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedTypes := map[string]bool{
|
||||||
|
"image/jpeg": true,
|
||||||
|
"image/jpg": true,
|
||||||
|
"image/png": true,
|
||||||
|
"image/webp": true,
|
||||||
|
}
|
||||||
|
contentType := file.Header.Get("Content-Type")
|
||||||
|
if !allowedTypes[contentType] {
|
||||||
|
h.response.BadRequest(c, "只支持 JPG、PNG、WEBP 格式的图片")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if file.Size > 5*1024*1024 {
|
||||||
|
h.response.BadRequest(c, "图片大小不能超过 5MB")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
src, err := file.Open()
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("打开上传文件失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
|
h.response.BadRequest(c, "文件读取失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer src.Close()
|
||||||
|
|
||||||
|
fileBytes, err := io.ReadAll(src)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("读取文件内容失败", zap.Error(err), zap.String("user_id", userID))
|
||||||
|
h.response.BadRequest(c, "文件读取失败")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
uploadResult, err := h.storageService.UploadFile(c.Request.Context(), fileBytes, file.Filename)
|
||||||
|
if err != nil {
|
||||||
|
h.logger.Error("上传文件到七牛云失败", zap.Error(err), zap.String("user_id", userID), zap.String("file_name", file.Filename))
|
||||||
|
h.response.BadRequest(c, "图片上传失败,请稍后重试")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
h.response.Success(c, map[string]string{
|
||||||
|
"url": uploadResult.URL,
|
||||||
|
"key": uploadResult.Key,
|
||||||
|
}, "上传成功")
|
||||||
|
}
|
||||||
|
|
||||||
// ListCertifications 获取认证列表(管理员)
|
// ListCertifications 获取认证列表(管理员)
|
||||||
// @Summary 获取认证列表
|
// @Summary 获取认证列表
|
||||||
// @Description 管理员获取认证申请列表
|
// @Description 管理员获取认证申请列表
|
||||||
@@ -375,6 +451,119 @@ func (h *CertificationHandler) AdminCompleteCertificationWithoutContract(c *gin.
|
|||||||
h.response.Success(c, result, "代用户完成认证成功")
|
h.response.Success(c, result, "代用户完成认证成功")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// AdminListSubmitRecords 管理端分页查询企业信息提交记录
|
||||||
|
// @Summary 管理端企业审核列表
|
||||||
|
// @Tags 认证管理
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param page query int false "页码"
|
||||||
|
// @Param page_size query int false "每页条数"
|
||||||
|
// @Param manual_review_status query string false "审核状态 pending/approved/rejected"
|
||||||
|
// @Success 200 {object} responses.AdminSubmitRecordsListResponse
|
||||||
|
// @Router /api/v1/certifications/admin/submit-records [get]
|
||||||
|
func (h *CertificationHandler) AdminListSubmitRecords(c *gin.Context) {
|
||||||
|
query := &queries.AdminListSubmitRecordsQuery{}
|
||||||
|
if err := c.ShouldBindQuery(query); err != nil {
|
||||||
|
h.response.BadRequest(c, "参数错误")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := h.appService.AdminListSubmitRecords(c.Request.Context(), query)
|
||||||
|
if err != nil {
|
||||||
|
h.response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.response.Success(c, result, "获取成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminGetSubmitRecordByID 管理端获取单条提交记录详情
|
||||||
|
// @Summary 管理端企业审核详情
|
||||||
|
// @Tags 认证管理
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "记录ID"
|
||||||
|
// @Success 200 {object} responses.AdminSubmitRecordDetail
|
||||||
|
// @Router /api/v1/certifications/admin/submit-records/{id} [get]
|
||||||
|
func (h *CertificationHandler) AdminGetSubmitRecordByID(c *gin.Context) {
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
h.response.BadRequest(c, "记录ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
result, err := h.appService.AdminGetSubmitRecordByID(c.Request.Context(), id)
|
||||||
|
if err != nil {
|
||||||
|
h.response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.response.Success(c, result, "获取成功")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminApproveSubmitRecord 管理端审核通过
|
||||||
|
// @Summary 管理端企业审核通过
|
||||||
|
// @Tags 认证管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "记录ID"
|
||||||
|
// @Param request body object true "可选 remark"
|
||||||
|
// @Success 200 {object} map[string]interface{}
|
||||||
|
// @Router /api/v1/certifications/admin/submit-records/{id}/approve [post]
|
||||||
|
func (h *CertificationHandler) AdminApproveSubmitRecord(c *gin.Context) {
|
||||||
|
adminID := h.getCurrentUserID(c)
|
||||||
|
if adminID == "" {
|
||||||
|
h.response.Unauthorized(c, "未登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
h.response.BadRequest(c, "记录ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body struct {
|
||||||
|
Remark string `json:"remark"`
|
||||||
|
}
|
||||||
|
_ = c.ShouldBindJSON(&body)
|
||||||
|
if err := h.appService.AdminApproveSubmitRecord(c.Request.Context(), id, adminID, body.Remark); err != nil {
|
||||||
|
h.response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.response.Success(c, nil, "审核通过")
|
||||||
|
}
|
||||||
|
|
||||||
|
// AdminRejectSubmitRecord 管理端审核拒绝
|
||||||
|
// @Summary 管理端企业审核拒绝
|
||||||
|
// @Tags 认证管理
|
||||||
|
// @Accept json
|
||||||
|
// @Produce json
|
||||||
|
// @Security Bearer
|
||||||
|
// @Param id path string true "记录ID"
|
||||||
|
// @Param request body object true "remark 必填"
|
||||||
|
// @Success 200 {object} map[string]interface{}
|
||||||
|
// @Router /api/v1/certifications/admin/submit-records/{id}/reject [post]
|
||||||
|
func (h *CertificationHandler) AdminRejectSubmitRecord(c *gin.Context) {
|
||||||
|
adminID := h.getCurrentUserID(c)
|
||||||
|
if adminID == "" {
|
||||||
|
h.response.Unauthorized(c, "未登录")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
id := c.Param("id")
|
||||||
|
if id == "" {
|
||||||
|
h.response.BadRequest(c, "记录ID不能为空")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var body struct {
|
||||||
|
Remark string `json:"remark" binding:"required"`
|
||||||
|
}
|
||||||
|
if err := c.ShouldBindJSON(&body); err != nil {
|
||||||
|
h.response.BadRequest(c, "请填写拒绝原因(remark)")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := h.appService.AdminRejectSubmitRecord(c.Request.Context(), id, adminID, body.Remark); err != nil {
|
||||||
|
h.response.BadRequest(c, err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
h.response.Success(c, nil, "已拒绝")
|
||||||
|
}
|
||||||
|
|
||||||
// ================ 回调处理 ================
|
// ================ 回调处理 ================
|
||||||
|
|
||||||
// HandleEsignCallback 处理e签宝回调
|
// HandleEsignCallback 处理e签宝回调
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ type CertificationRoutes struct {
|
|||||||
router *http.GinRouter
|
router *http.GinRouter
|
||||||
logger *zap.Logger
|
logger *zap.Logger
|
||||||
auth *middleware.JWTAuthMiddleware
|
auth *middleware.JWTAuthMiddleware
|
||||||
|
admin *middleware.AdminAuthMiddleware
|
||||||
optional *middleware.OptionalAuthMiddleware
|
optional *middleware.OptionalAuthMiddleware
|
||||||
dailyRateLimit *middleware.DailyRateLimitMiddleware
|
dailyRateLimit *middleware.DailyRateLimitMiddleware
|
||||||
}
|
}
|
||||||
@@ -24,6 +25,7 @@ func NewCertificationRoutes(
|
|||||||
router *http.GinRouter,
|
router *http.GinRouter,
|
||||||
logger *zap.Logger,
|
logger *zap.Logger,
|
||||||
auth *middleware.JWTAuthMiddleware,
|
auth *middleware.JWTAuthMiddleware,
|
||||||
|
admin *middleware.AdminAuthMiddleware,
|
||||||
optional *middleware.OptionalAuthMiddleware,
|
optional *middleware.OptionalAuthMiddleware,
|
||||||
dailyRateLimit *middleware.DailyRateLimitMiddleware,
|
dailyRateLimit *middleware.DailyRateLimitMiddleware,
|
||||||
) *CertificationRoutes {
|
) *CertificationRoutes {
|
||||||
@@ -32,6 +34,7 @@ func NewCertificationRoutes(
|
|||||||
router: router,
|
router: router,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
auth: auth,
|
auth: auth,
|
||||||
|
admin: admin,
|
||||||
optional: optional,
|
optional: optional,
|
||||||
dailyRateLimit: dailyRateLimit,
|
dailyRateLimit: dailyRateLimit,
|
||||||
}
|
}
|
||||||
@@ -57,6 +60,9 @@ func (r *CertificationRoutes) Register(router *http.GinRouter) {
|
|||||||
// OCR营业执照识别接口
|
// OCR营业执照识别接口
|
||||||
authGroup.POST("/ocr/business-license", r.handler.RecognizeBusinessLicense)
|
authGroup.POST("/ocr/business-license", r.handler.RecognizeBusinessLicense)
|
||||||
|
|
||||||
|
// 认证图片上传(七牛云,用于企业信息中的各类图片)
|
||||||
|
authGroup.POST("/upload", r.handler.UploadCertificationFile)
|
||||||
|
|
||||||
// 3. 申请合同签署
|
// 3. 申请合同签署
|
||||||
authGroup.POST("/apply-contract", r.handler.ApplyContract)
|
authGroup.POST("/apply-contract", r.handler.ApplyContract)
|
||||||
|
|
||||||
@@ -71,6 +77,17 @@ func (r *CertificationRoutes) Register(router *http.GinRouter) {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 管理端企业审核(需管理员权限)
|
||||||
|
adminCertGroup := certificationGroup.Group("/admin/submit-records")
|
||||||
|
adminCertGroup.Use(r.auth.Handle())
|
||||||
|
adminCertGroup.Use(r.admin.Handle())
|
||||||
|
{
|
||||||
|
adminCertGroup.GET("", r.handler.AdminListSubmitRecords)
|
||||||
|
adminCertGroup.GET("/:id", r.handler.AdminGetSubmitRecordByID)
|
||||||
|
adminCertGroup.POST("/:id/approve", r.handler.AdminApproveSubmitRecord)
|
||||||
|
adminCertGroup.POST("/:id/reject", r.handler.AdminRejectSubmitRecord)
|
||||||
|
}
|
||||||
|
|
||||||
// 回调路由(不需要认证,但需要验证签名)
|
// 回调路由(不需要认证,但需要验证签名)
|
||||||
callbackGroup := certificationGroup.Group("/callbacks")
|
callbackGroup := certificationGroup.Group("/callbacks")
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -97,7 +97,10 @@ func (m *DailyRateLimitMiddleware) GetPriority() int {
|
|||||||
func (m *DailyRateLimitMiddleware) Handle() gin.HandlerFunc {
|
func (m *DailyRateLimitMiddleware) Handle() gin.HandlerFunc {
|
||||||
return func(c *gin.Context) {
|
return func(c *gin.Context) {
|
||||||
ctx := c.Request.Context()
|
ctx := c.Request.Context()
|
||||||
|
if m.config.App.IsDevelopment() {
|
||||||
|
c.Next()
|
||||||
|
return
|
||||||
|
}
|
||||||
// 检查是否在排除路径中
|
// 检查是否在排除路径中
|
||||||
if m.isExcludedPath(c.Request.URL.Path) {
|
if m.isExcludedPath(c.Request.URL.Path) {
|
||||||
c.Next()
|
c.Next()
|
||||||
|
|||||||
@@ -1139,7 +1139,7 @@ func (pb *PageBuilder) addWatermark(pdf *gofpdf.Fpdf, chineseFontAvailable bool)
|
|||||||
// 段前缩进宽度(约两字符,mm)
|
// 段前缩进宽度(约两字符,mm)
|
||||||
const paragraphIndentMM = 7.0
|
const paragraphIndentMM = 7.0
|
||||||
|
|
||||||
// drawRichTextBlock 按段落与换行绘制文本块(还原 HTML 换行),超出 maxContentY 截断并显示 …
|
// drawRichTextBlock 按段落与换行绘制文本块(还原 HTML 换行)
|
||||||
// align: "C" 居中;"L" 左对齐。firstLineIndent 为 true 时每段首行缩进(段前两空格效果)。
|
// align: "C" 居中;"L" 左对齐。firstLineIndent 为 true 时每段首行缩进(段前两空格效果)。
|
||||||
func (pb *PageBuilder) drawRichTextBlock(pdf *gofpdf.Fpdf, text string, contentWidth, lineHeight float64, maxContentY float64, align string, firstLineIndent bool, chineseFontAvailable bool) {
|
func (pb *PageBuilder) drawRichTextBlock(pdf *gofpdf.Fpdf, text string, contentWidth, lineHeight float64, maxContentY float64, align string, firstLineIndent bool, chineseFontAvailable bool) {
|
||||||
pageWidth, _ := pdf.GetPageSize()
|
pageWidth, _ := pdf.GetPageSize()
|
||||||
|
|||||||
Reference in New Issue
Block a user