f
This commit is contained in:
818
internal/domains/certification/entities/certification.go
Normal file
818
internal/domains/certification/entities/certification.go
Normal file
@@ -0,0 +1,818 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"hyapi-server/internal/domains/certification/entities/value_objects"
|
||||
"hyapi-server/internal/domains/certification/enums"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// Certification 认证聚合根
|
||||
// 这是企业认证流程的核心聚合根,封装了完整的认证业务逻辑和状态管理
|
||||
type Certification struct {
|
||||
// === 基础信息 ===
|
||||
ID string `gorm:"primaryKey;type:varchar(64)" json:"id" comment:"认证申请唯一标识"`
|
||||
UserID string `gorm:"type:varchar(36);not null;unique" json:"user_id" comment:"申请用户ID"`
|
||||
Status enums.CertificationStatus `gorm:"type:varchar(50);not null;index" json:"status" comment:"当前认证状态"`
|
||||
|
||||
// === 流程时间戳 - 记录每个关键步骤的完成时间 ===
|
||||
InfoSubmittedAt *time.Time `json:"info_submitted_at,omitempty" comment:"企业信息提交时间"`
|
||||
EnterpriseVerifiedAt *time.Time `json:"enterprise_verified_at,omitempty" comment:"企业认证完成时间"`
|
||||
ContractAppliedAt *time.Time `json:"contract_applied_at,omitempty" comment:"合同申请时间"`
|
||||
ContractSignedAt *time.Time `json:"contract_signed_at,omitempty" comment:"合同签署完成时间"`
|
||||
CompletedAt *time.Time `json:"completed_at,omitempty" comment:"认证完成时间"`
|
||||
ContractFileCreatedAt *time.Time `json:"contract_file_created_at,omitempty" comment:"合同文件生成时间"`
|
||||
|
||||
// === e签宝相关信息 ===
|
||||
AuthFlowID string `gorm:"type:varchar(500)" json:"auth_flow_id,omitempty" comment:"企业认证流程ID"`
|
||||
AuthURL string `gorm:"type:varchar(500)" json:"auth_url,omitempty" comment:"企业认证链接"`
|
||||
ContractFileID string `gorm:"type:varchar(500)" json:"contract_file_id,omitempty" comment:"合同文件ID"`
|
||||
EsignFlowID string `gorm:"type:varchar(500)" json:"esign_flow_id,omitempty" comment:"签署流程ID"`
|
||||
ContractURL string `gorm:"type:varchar(500)" json:"contract_url,omitempty" comment:"合同文件访问链接"`
|
||||
ContractSignURL string `gorm:"type:varchar(500)" json:"contract_sign_url,omitempty" comment:"合同签署链接"`
|
||||
|
||||
// === 失败信息 ===
|
||||
FailureReason enums.FailureReason `gorm:"type:varchar(100)" json:"failure_reason,omitempty" comment:"失败原因"`
|
||||
FailureMessage string `gorm:"type:text" json:"failure_message,omitempty" comment:"失败详细信息"`
|
||||
RetryCount int `gorm:"default:0" json:"retry_count" comment:"重试次数"`
|
||||
|
||||
// === 审计信息 ===
|
||||
LastTransitionAt *time.Time `json:"last_transition_at,omitempty" comment:"最后状态转换时间"`
|
||||
LastTransitionBy enums.ActorType `gorm:"type:varchar(20)" json:"last_transition_by,omitempty" comment:"最后操作者类型"`
|
||||
LastTransitionActor string `gorm:"type:varchar(100)" json:"last_transition_actor,omitempty" comment:"最后操作者ID"`
|
||||
|
||||
// === 系统字段 ===
|
||||
CreatedAt time.Time `gorm:"autoCreateTime" json:"created_at" comment:"创建时间"`
|
||||
UpdatedAt time.Time `gorm:"autoUpdateTime" json:"updated_at" comment:"更新时间"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index" json:"-" comment:"软删除时间"`
|
||||
|
||||
// === 领域事件 (不持久化) ===
|
||||
domainEvents []interface{} `gorm:"-" json:"-"`
|
||||
}
|
||||
|
||||
// TableName 指定数据库表名
|
||||
func (Certification) TableName() string {
|
||||
return "certifications"
|
||||
}
|
||||
|
||||
// BeforeCreate GORM钩子:创建前自动生成UUID
|
||||
func (c *Certification) BeforeCreate(tx *gorm.DB) error {
|
||||
if c.ID == "" {
|
||||
c.ID = uuid.New().String()
|
||||
}
|
||||
|
||||
// 设置初始状态
|
||||
if c.Status == "" {
|
||||
c.Status = enums.StatusPending
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 工厂方法 ================
|
||||
|
||||
// NewCertification 创建新的认证申请
|
||||
func NewCertification(userID string) (*Certification, error) {
|
||||
if userID == "" {
|
||||
return nil, errors.New("用户ID不能为空")
|
||||
}
|
||||
|
||||
certification := &Certification{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
Status: enums.StatusPending,
|
||||
RetryCount: 0,
|
||||
domainEvents: make([]interface{}, 0),
|
||||
}
|
||||
|
||||
// 添加领域事件
|
||||
certification.addDomainEvent(&CertificationCreatedEvent{
|
||||
CertificationID: certification.ID,
|
||||
UserID: userID,
|
||||
CreatedAt: time.Now(),
|
||||
})
|
||||
|
||||
return certification, nil
|
||||
}
|
||||
|
||||
// ================ 状态转换方法 ================
|
||||
|
||||
// CanTransitionTo 检查是否可以转换到目标状态
|
||||
func (c *Certification) CanTransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType) (bool, string) {
|
||||
// 检查状态转换规则
|
||||
if !enums.CanTransitionTo(c.Status, targetStatus) {
|
||||
return false, fmt.Sprintf("不允许从 %s 转换到 %s", enums.GetStatusName(c.Status), enums.GetStatusName(targetStatus))
|
||||
}
|
||||
|
||||
// 检查操作者权限
|
||||
if !c.validateActorPermission(targetStatus, actor) {
|
||||
return false, fmt.Sprintf("%s 无权执行此状态转换", enums.GetActorTypeName(actor))
|
||||
}
|
||||
return true, ""
|
||||
}
|
||||
|
||||
// TransitionTo 执行状态转换
|
||||
func (c *Certification) TransitionTo(targetStatus enums.CertificationStatus, actor enums.ActorType, actorID string, reason string) error {
|
||||
// 验证转换合法性
|
||||
canTransition, message := c.CanTransitionTo(targetStatus, actor)
|
||||
if !canTransition {
|
||||
return fmt.Errorf("状态转换失败: %s", message)
|
||||
}
|
||||
|
||||
oldStatus := c.Status
|
||||
|
||||
// 执行状态转换
|
||||
c.Status = targetStatus
|
||||
c.updateTimestampByStatus(targetStatus)
|
||||
c.updateTransitionAudit(actor, actorID)
|
||||
|
||||
// 清除失败信息(如果转换到成功状态)
|
||||
if !enums.IsFailureStatus(targetStatus) {
|
||||
c.clearFailureInfo()
|
||||
}
|
||||
|
||||
// 添加状态转换事件
|
||||
c.addDomainEvent(&CertificationStatusChangedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
FromStatus: oldStatus,
|
||||
ToStatus: targetStatus,
|
||||
Actor: actor,
|
||||
ActorID: actorID,
|
||||
Reason: reason,
|
||||
TransitionedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 业务操作方法 ================
|
||||
|
||||
// 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 {
|
||||
// 验证当前状态
|
||||
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 authURL != "" {
|
||||
c.AuthURL = authURL
|
||||
}
|
||||
if authFlowID != "" {
|
||||
c.AuthFlowID = authFlowID
|
||||
}
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(enums.StatusInfoSubmitted, enums.ActorTypeUser, c.UserID, "用户提交企业信息"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加业务事件
|
||||
c.addDomainEvent(&EnterpriseInfoSubmittedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
EnterpriseInfo: enterpriseInfo,
|
||||
SubmittedAt: time.Now(),
|
||||
})
|
||||
|
||||
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 {
|
||||
if c.Status != enums.StatusInfoSubmitted {
|
||||
return fmt.Errorf("当前状态 %s 不允许完成企业认证", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeSystem, "system", "企业认证成功"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
AuthFlowID: "",
|
||||
VerifiedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// HandleEnterpriseVerificationCallback 处理企业认证回调
|
||||
func (c *Certification) HandleEnterpriseVerificationCallback(success bool, authFlowID string, failureReason enums.FailureReason, message string) error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusInfoSubmitted {
|
||||
return fmt.Errorf("当前状态 %s 不允许处理企业认证回调", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
c.AuthFlowID = authFlowID
|
||||
|
||||
if success {
|
||||
// 认证成功
|
||||
if err := c.TransitionTo(enums.StatusEnterpriseVerified, enums.ActorTypeEsign, "esign_system", "企业认证成功"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.addDomainEvent(&EnterpriseVerificationSuccessEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
AuthFlowID: authFlowID,
|
||||
VerifiedAt: time.Now(),
|
||||
})
|
||||
} else {
|
||||
// 认证失败
|
||||
c.setFailureInfo(failureReason, message)
|
||||
|
||||
if err := c.TransitionTo(enums.StatusInfoRejected, enums.ActorTypeEsign, "esign_system", "企业认证失败"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.addDomainEvent(&EnterpriseVerificationFailedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
AuthFlowID: authFlowID,
|
||||
FailureReason: failureReason,
|
||||
FailureMessage: message,
|
||||
FailedAt: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ApplyContract 申请合同签署
|
||||
func (c *Certification) ApplyContract(EsignFlowID string, ContractSignURL string) error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusEnterpriseVerified {
|
||||
return fmt.Errorf("当前状态 %s 不允许申请合同", enums.GetStatusName(c.Status))
|
||||
}
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(enums.StatusContractApplied, enums.ActorTypeUser, c.UserID, "用户申请合同签署"); err != nil {
|
||||
return err
|
||||
}
|
||||
c.EsignFlowID = EsignFlowID
|
||||
c.ContractSignURL = ContractSignURL
|
||||
now := time.Now()
|
||||
c.ContractFileCreatedAt = &now
|
||||
// 添加业务事件
|
||||
c.addDomainEvent(&ContractAppliedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
AppliedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddContractFileID 生成合同文件
|
||||
func (c *Certification) AddContractFileID(contractFileID string, contractURL string) error {
|
||||
c.ContractFileID = contractFileID
|
||||
c.ContractURL = contractURL
|
||||
now := time.Now()
|
||||
c.ContractFileCreatedAt = &now
|
||||
return nil
|
||||
}
|
||||
|
||||
// UpdateContractInfo 更新合同信息
|
||||
func (c *Certification) UpdateContractInfo(contractInfo *value_objects.ContractInfo) error {
|
||||
// 验证合同信息
|
||||
if err := contractInfo.Validate(); err != nil {
|
||||
return fmt.Errorf("合同信息验证失败: %w", err)
|
||||
}
|
||||
|
||||
// 更新合同相关字段
|
||||
c.ContractFileID = contractInfo.ContractFileID
|
||||
c.EsignFlowID = contractInfo.EsignFlowID
|
||||
c.ContractURL = contractInfo.ContractURL
|
||||
c.ContractSignURL = contractInfo.ContractSignURL
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// SignSuccess 签署成功
|
||||
func (c *Certification) SignSuccess() error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusContractApplied {
|
||||
return fmt.Errorf("当前状态 %s 不允许处理合同签署回调", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
if err := c.TransitionTo(enums.StatusContractSigned, enums.ActorTypeEsign, "esign_system", "合同签署成功"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
c.addDomainEvent(&ContractSignedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
SignedAt: time.Now(),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContractRejection 处理合同拒签
|
||||
func (c *Certification) ContractRejection(message string) error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusContractApplied {
|
||||
return fmt.Errorf("当前状态 %s 不允许处理合同拒签", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
// 设置失败信息
|
||||
c.setFailureInfo(enums.FailureReasonContractRejectedByUser, message)
|
||||
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(enums.StatusContractRejected, enums.ActorTypeEsign, "esign_system", "合同签署被拒绝"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加业务事件
|
||||
c.addDomainEvent(&ContractSignFailedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
FailureReason: enums.FailureReasonContractRejectedByUser,
|
||||
FailureMessage: message,
|
||||
FailedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ContractExpiration 处理合同过期
|
||||
func (c *Certification) ContractExpiration() error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusContractApplied {
|
||||
return fmt.Errorf("当前状态 %s 不允许处理合同过期", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
// 设置失败信息
|
||||
c.setFailureInfo(enums.FailureReasonContractExpired, "合同签署已超时")
|
||||
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(enums.StatusContractExpired, enums.ActorTypeSystem, "system", "合同签署超时"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加业务事件
|
||||
c.addDomainEvent(&ContractSignFailedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
FailureReason: enums.FailureReasonContractExpired,
|
||||
FailureMessage: "合同签署已超时",
|
||||
FailedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RetryFromFailure 从失败状态重试
|
||||
func (c *Certification) RetryFromFailure(actor enums.ActorType, actorID string) error {
|
||||
if !enums.IsFailureStatus(c.Status) {
|
||||
return errors.New("当前状态不是失败状态,无需重试")
|
||||
}
|
||||
|
||||
// 检查重试次数限制
|
||||
if c.RetryCount >= 3 {
|
||||
return errors.New("已达到最大重试次数限制")
|
||||
}
|
||||
|
||||
// 检查失败原因是否可重试
|
||||
if !enums.IsRetryable(c.FailureReason) {
|
||||
return fmt.Errorf("失败原因 %s 不支持重试", enums.GetFailureReasonName(c.FailureReason))
|
||||
}
|
||||
|
||||
var targetStatus enums.CertificationStatus
|
||||
var reason string
|
||||
|
||||
switch c.Status {
|
||||
case enums.StatusInfoRejected:
|
||||
targetStatus = enums.StatusInfoSubmitted
|
||||
reason = "重新提交企业信息"
|
||||
case enums.StatusContractRejected, enums.StatusContractExpired:
|
||||
targetStatus = enums.StatusEnterpriseVerified
|
||||
reason = "重置状态,准备重新申请合同"
|
||||
default:
|
||||
return fmt.Errorf("不支持从状态 %s 重试", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
// 增加重试次数
|
||||
c.RetryCount++
|
||||
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(targetStatus, actor, actorID, reason); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加重试事件
|
||||
c.addDomainEvent(&CertificationRetryEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
FromStatus: c.Status,
|
||||
ToStatus: targetStatus,
|
||||
RetryCount: c.RetryCount,
|
||||
RetriedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// CompleteCertification 完成认证
|
||||
func (c *Certification) CompleteCertification() error {
|
||||
// 验证当前状态
|
||||
if c.Status != enums.StatusContractSigned {
|
||||
return fmt.Errorf("当前状态 %s 不允许完成认证", enums.GetStatusName(c.Status))
|
||||
}
|
||||
|
||||
// 状态转换
|
||||
if err := c.TransitionTo(enums.StatusCompleted, enums.ActorTypeSystem, "system", "系统处理完成,认证成功"); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 添加业务事件
|
||||
c.addDomainEvent(&CertificationCompletedEvent{
|
||||
CertificationID: c.ID,
|
||||
UserID: c.UserID,
|
||||
CompletedAt: time.Now(),
|
||||
})
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ================ 查询方法 ================
|
||||
// GetDataByStatus 根据当前状态获取对应的数据
|
||||
func (c *Certification) GetDataByStatus() map[string]interface{} {
|
||||
data := map[string]interface{}{}
|
||||
switch c.Status {
|
||||
case enums.StatusInfoPendingReview:
|
||||
// 待审核,无额外数据
|
||||
case enums.StatusInfoSubmitted:
|
||||
data["auth_url"] = c.AuthURL
|
||||
case enums.StatusInfoRejected:
|
||||
data["failure_reason"] = c.FailureReason
|
||||
data["failure_message"] = c.FailureMessage
|
||||
case enums.StatusEnterpriseVerified:
|
||||
data["ContractURL"] = c.ContractURL
|
||||
case enums.StatusContractApplied:
|
||||
data["contract_sign_url"] = c.ContractSignURL
|
||||
case enums.StatusContractSigned:
|
||||
case enums.StatusCompleted:
|
||||
data["completed_at"] = c.CompletedAt
|
||||
case enums.StatusContractRejected:
|
||||
data["failure_reason"] = c.FailureReason
|
||||
data["failure_message"] = c.FailureMessage
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
// GetProgress 获取认证进度百分比
|
||||
func (c *Certification) GetProgress() int {
|
||||
return enums.GetProgressPercentage(c.Status)
|
||||
}
|
||||
|
||||
// IsUserActionRequired 是否需要用户操作
|
||||
func (c *Certification) IsUserActionRequired() bool {
|
||||
return enums.IsUserActionRequired(c.Status)
|
||||
}
|
||||
|
||||
// GetCurrentStatusName 获取当前状态名称
|
||||
func (c *Certification) GetCurrentStatusName() string {
|
||||
return enums.GetStatusName(c.Status)
|
||||
}
|
||||
|
||||
// GetUserActionHint 获取用户操作提示
|
||||
func (c *Certification) GetUserActionHint() string {
|
||||
return enums.GetUserActionHint(c.Status)
|
||||
}
|
||||
|
||||
// GetAvailableActions 获取当前可执行的操作
|
||||
func (c *Certification) GetAvailableActions() []string {
|
||||
actions := make([]string, 0)
|
||||
|
||||
switch c.Status {
|
||||
case enums.StatusPending:
|
||||
actions = append(actions, "submit_enterprise_info")
|
||||
case enums.StatusInfoPendingReview:
|
||||
// 等待人工审核,无用户操作
|
||||
case enums.StatusEnterpriseVerified:
|
||||
actions = append(actions, "apply_contract")
|
||||
case enums.StatusInfoRejected, enums.StatusContractRejected, enums.StatusContractExpired:
|
||||
if enums.IsRetryable(c.FailureReason) && c.RetryCount < 3 {
|
||||
actions = append(actions, "retry")
|
||||
}
|
||||
}
|
||||
|
||||
return actions
|
||||
}
|
||||
|
||||
// IsFinalStatus 是否为最终状态
|
||||
func (c *Certification) IsFinalStatus() bool {
|
||||
return enums.IsFinalStatus(c.Status)
|
||||
}
|
||||
|
||||
// IsCompleted 是否已完成
|
||||
func (c *Certification) IsCompleted() bool {
|
||||
return c.Status == enums.StatusCompleted
|
||||
}
|
||||
|
||||
// GetNextValidStatuses 获取下一个有效状态
|
||||
func (c *Certification) GetNextValidStatuses() []enums.CertificationStatus {
|
||||
return enums.GetNextValidStatuses(c.Status)
|
||||
}
|
||||
|
||||
// GetFailureInfo 获取失败信息
|
||||
func (c *Certification) GetFailureInfo() (enums.FailureReason, string) {
|
||||
return c.FailureReason, c.FailureMessage
|
||||
}
|
||||
|
||||
// IsContractFileExpired 判断合同文件是否过期(生成后50分钟过期)
|
||||
func (c *Certification) IsContractFileExpired() bool {
|
||||
if c.ContractFileCreatedAt == nil && c.Status == enums.StatusEnterpriseVerified {
|
||||
// 60分钟前
|
||||
t := time.Now().Add(-60 * time.Minute)
|
||||
c.ContractFileCreatedAt = &t
|
||||
return true
|
||||
}
|
||||
if c.ContractFileCreatedAt != nil {
|
||||
return time.Since(*c.ContractFileCreatedAt) > 50*time.Minute
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// IsContractFileNeedUpdate 是否需要更新合同文件
|
||||
func (c *Certification) IsContractFileNeedUpdate() bool {
|
||||
if c.IsContractFileExpired() && c.Status == enums.StatusEnterpriseVerified {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// ================ 业务规则验证 ================
|
||||
|
||||
// ValidateBusinessRules 验证业务规则
|
||||
func (c *Certification) ValidateBusinessRules() error {
|
||||
// 基础验证
|
||||
if c.UserID == "" {
|
||||
return errors.New("用户ID不能为空")
|
||||
}
|
||||
|
||||
if !enums.IsValidStatus(c.Status) {
|
||||
return fmt.Errorf("无效的认证状态: %s", c.Status)
|
||||
}
|
||||
|
||||
// 状态相关验证
|
||||
switch c.Status {
|
||||
case enums.StatusContractSigned:
|
||||
if c.ContractFileID == "" || c.EsignFlowID == "" {
|
||||
return errors.New("合同签署状态下必须有完整的合同信息")
|
||||
}
|
||||
case enums.StatusCompleted:
|
||||
if c.CompletedAt == nil {
|
||||
return errors.New("认证完成状态下必须有完成时间")
|
||||
}
|
||||
}
|
||||
|
||||
// 失败状态验证
|
||||
if enums.IsFailureStatus(c.Status) {
|
||||
if c.FailureReason == "" {
|
||||
return errors.New("失败状态下必须有失败原因")
|
||||
}
|
||||
if !enums.IsValidFailureReason(c.FailureReason) {
|
||||
return fmt.Errorf("无效的失败原因: %s", c.FailureReason)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateActorPermission 验证操作者权限
|
||||
func (c *Certification) validateActorPermission(targetStatus enums.CertificationStatus, actor enums.ActorType) bool {
|
||||
// 定义状态转换的权限规则(目标状态 -> 允许的操作者)
|
||||
permissions := map[enums.CertificationStatus][]enums.ActorType{
|
||||
enums.StatusInfoPendingReview: {enums.ActorTypeUser},
|
||||
enums.StatusInfoSubmitted: {enums.ActorTypeUser, enums.ActorTypeAdmin},
|
||||
enums.StatusEnterpriseVerified: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
enums.StatusInfoRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
enums.StatusContractApplied: {enums.ActorTypeUser, enums.ActorTypeAdmin},
|
||||
enums.StatusContractSigned: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
enums.StatusContractRejected: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
enums.StatusContractExpired: {enums.ActorTypeEsign, enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
enums.StatusCompleted: {enums.ActorTypeSystem, enums.ActorTypeAdmin},
|
||||
}
|
||||
|
||||
allowedActors, exists := permissions[targetStatus]
|
||||
if !exists {
|
||||
return false
|
||||
}
|
||||
|
||||
for _, allowedActor := range allowedActors {
|
||||
if actor == allowedActor {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
// ================ 辅助方法 ================
|
||||
|
||||
// updateTimestampByStatus 根据状态更新对应的时间戳
|
||||
func (c *Certification) updateTimestampByStatus(status enums.CertificationStatus) {
|
||||
now := time.Now()
|
||||
|
||||
switch status {
|
||||
case enums.StatusInfoSubmitted:
|
||||
c.InfoSubmittedAt = &now
|
||||
case enums.StatusEnterpriseVerified:
|
||||
c.EnterpriseVerifiedAt = &now
|
||||
case enums.StatusContractApplied:
|
||||
c.ContractAppliedAt = &now
|
||||
case enums.StatusContractSigned:
|
||||
c.ContractSignedAt = &now
|
||||
case enums.StatusCompleted:
|
||||
c.CompletedAt = &now
|
||||
}
|
||||
}
|
||||
|
||||
// updateTransitionAudit 更新状态转换审计信息
|
||||
func (c *Certification) updateTransitionAudit(actor enums.ActorType, actorID string) {
|
||||
now := time.Now()
|
||||
c.LastTransitionAt = &now
|
||||
c.LastTransitionBy = actor
|
||||
c.LastTransitionActor = actorID
|
||||
}
|
||||
|
||||
// setFailureInfo 设置失败信息
|
||||
func (c *Certification) setFailureInfo(reason enums.FailureReason, message string) {
|
||||
c.FailureReason = reason
|
||||
c.FailureMessage = message
|
||||
}
|
||||
|
||||
// clearFailureInfo 清除失败信息
|
||||
func (c *Certification) clearFailureInfo() {
|
||||
c.FailureReason = ""
|
||||
c.FailureMessage = ""
|
||||
}
|
||||
|
||||
// ================ 领域事件管理 ================
|
||||
|
||||
// addDomainEvent 添加领域事件
|
||||
func (c *Certification) addDomainEvent(event interface{}) {
|
||||
if c.domainEvents == nil {
|
||||
c.domainEvents = make([]interface{}, 0)
|
||||
}
|
||||
c.domainEvents = append(c.domainEvents, event)
|
||||
}
|
||||
|
||||
// GetDomainEvents 获取领域事件
|
||||
func (c *Certification) GetDomainEvents() []interface{} {
|
||||
return c.domainEvents
|
||||
}
|
||||
|
||||
// ClearDomainEvents 清除领域事件
|
||||
func (c *Certification) ClearDomainEvents() {
|
||||
c.domainEvents = make([]interface{}, 0)
|
||||
}
|
||||
|
||||
// ================ 领域事件定义 ================
|
||||
|
||||
// CertificationCreatedEvent 认证创建事件
|
||||
type CertificationCreatedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
CreatedAt time.Time `json:"created_at"`
|
||||
}
|
||||
|
||||
// CertificationStatusChangedEvent 认证状态变更事件
|
||||
type CertificationStatusChangedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
FromStatus enums.CertificationStatus `json:"from_status"`
|
||||
ToStatus enums.CertificationStatus `json:"to_status"`
|
||||
Actor enums.ActorType `json:"actor"`
|
||||
ActorID string `json:"actor_id"`
|
||||
Reason string `json:"reason"`
|
||||
TransitionedAt time.Time `json:"transitioned_at"`
|
||||
}
|
||||
|
||||
// EnterpriseInfoSubmittedEvent 企业信息提交事件
|
||||
type EnterpriseInfoSubmittedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
EnterpriseInfo *value_objects.EnterpriseInfo `json:"enterprise_info"`
|
||||
SubmittedAt time.Time `json:"submitted_at"`
|
||||
}
|
||||
|
||||
// EnterpriseVerificationSuccessEvent 企业认证成功事件
|
||||
type EnterpriseVerificationSuccessEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AuthFlowID string `json:"auth_flow_id"`
|
||||
VerifiedAt time.Time `json:"verified_at"`
|
||||
}
|
||||
|
||||
// EnterpriseVerificationFailedEvent 企业认证失败事件
|
||||
type EnterpriseVerificationFailedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AuthFlowID string `json:"auth_flow_id"`
|
||||
FailureReason enums.FailureReason `json:"failure_reason"`
|
||||
FailureMessage string `json:"failure_message"`
|
||||
FailedAt time.Time `json:"failed_at"`
|
||||
}
|
||||
|
||||
// ContractAppliedEvent 合同申请事件
|
||||
type ContractAppliedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
AppliedAt time.Time `json:"applied_at"`
|
||||
}
|
||||
|
||||
// ContractSignedEvent 合同签署成功事件
|
||||
type ContractSignedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
ContractURL string `json:"contract_url"`
|
||||
SignedAt time.Time `json:"signed_at"`
|
||||
}
|
||||
|
||||
// ContractSignFailedEvent 合同签署失败事件
|
||||
type ContractSignFailedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
FailureReason enums.FailureReason `json:"failure_reason"`
|
||||
FailureMessage string `json:"failure_message"`
|
||||
FailedAt time.Time `json:"failed_at"`
|
||||
}
|
||||
|
||||
// CertificationCompletedEvent 认证完成事件
|
||||
type CertificationCompletedEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
CompletedAt time.Time `json:"completed_at"`
|
||||
}
|
||||
|
||||
// CertificationRetryEvent 认证重试事件
|
||||
type CertificationRetryEvent struct {
|
||||
CertificationID string `json:"certification_id"`
|
||||
UserID string `json:"user_id"`
|
||||
FromStatus enums.CertificationStatus `json:"from_status"`
|
||||
ToStatus enums.CertificationStatus `json:"to_status"`
|
||||
RetryCount int `json:"retry_count"`
|
||||
RetriedAt time.Time `json:"retried_at"`
|
||||
}
|
||||
@@ -0,0 +1,127 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EnterpriseInfoSubmitRecord 企业信息提交记录
|
||||
type EnterpriseInfoSubmitRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
|
||||
// 企业信息
|
||||
CompanyName string `json:"company_name" gorm:"type:varchar(200);not null"`
|
||||
UnifiedSocialCode string `json:"unified_social_code" gorm:"type:varchar(50);not null;index"`
|
||||
LegalPersonName string `json:"legal_person_name" 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"`
|
||||
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
|
||||
SubmitAt time.Time `json:"submit_at" gorm:"not null"`
|
||||
VerifiedAt *time.Time `json:"verified_at"`
|
||||
FailedAt *time.Time `json:"failed_at"`
|
||||
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"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EnterpriseInfoSubmitRecord) TableName() string {
|
||||
return "enterprise_info_submit_records"
|
||||
}
|
||||
|
||||
// NewEnterpriseInfoSubmitRecord 创建新的企业信息提交记录
|
||||
func NewEnterpriseInfoSubmitRecord(
|
||||
userID, companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress string,
|
||||
) *EnterpriseInfoSubmitRecord {
|
||||
return &EnterpriseInfoSubmitRecord{
|
||||
ID: uuid.New().String(),
|
||||
UserID: userID,
|
||||
CompanyName: companyName,
|
||||
UnifiedSocialCode: unifiedSocialCode,
|
||||
LegalPersonName: legalPersonName,
|
||||
LegalPersonID: legalPersonID,
|
||||
LegalPersonPhone: legalPersonPhone,
|
||||
EnterpriseAddress: enterpriseAddress,
|
||||
Status: "submitted",
|
||||
ManualReviewStatus: "pending",
|
||||
SubmitAt: time.Now(),
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsVerified 标记为已验证
|
||||
func (r *EnterpriseInfoSubmitRecord) MarkAsVerified() {
|
||||
now := time.Now()
|
||||
r.Status = "verified"
|
||||
r.VerifiedAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsFailed 标记为验证失败
|
||||
func (r *EnterpriseInfoSubmitRecord) MarkAsFailed(reason string) {
|
||||
now := time.Now()
|
||||
r.Status = "failed"
|
||||
r.FailedAt = &now
|
||||
r.FailureReason = reason
|
||||
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 检查是否已验证
|
||||
func (r *EnterpriseInfoSubmitRecord) IsVerified() bool {
|
||||
return r.Status == "verified"
|
||||
}
|
||||
|
||||
// IsFailed 检查是否验证失败
|
||||
func (r *EnterpriseInfoSubmitRecord) IsFailed() bool {
|
||||
return r.Status == "failed"
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EsignContractGenerateRecord e签宝生成合同文件记录
|
||||
type EsignContractGenerateRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
|
||||
// e签宝相关
|
||||
TemplateID string `json:"template_id" gorm:"type:varchar(100);index"` // 模板ID
|
||||
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
|
||||
ContractURL string `json:"contract_url" gorm:"type:varchar(500)"` // 合同文件URL
|
||||
ContractName string `json:"contract_name" gorm:"type:varchar(200)"` // 合同名称
|
||||
|
||||
// 生成状态
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null"` // success, failed
|
||||
|
||||
FillTime *time.Time `json:"fill_time"` // 填写时间
|
||||
|
||||
// 系统字段
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EsignContractGenerateRecord) TableName() string {
|
||||
return "esign_contract_generate_records"
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
package entities
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// EsignContractSignRecord e签宝签署合同记录
|
||||
type EsignContractSignRecord struct {
|
||||
ID string `json:"id" gorm:"primaryKey;type:varchar(36)"`
|
||||
CertificationID string `json:"certification_id" gorm:"type:varchar(36);not null;index"`
|
||||
UserID string `json:"user_id" gorm:"type:varchar(36);not null;index"`
|
||||
EnterpriseInfoID string `json:"enterprise_info_id" gorm:"type:varchar(36);not null;index"` // 企业信息ID
|
||||
|
||||
// e签宝相关
|
||||
EsignFlowID string `json:"esign_flow_id" gorm:"type:varchar(100);index"` // e签宝流程ID
|
||||
ContractFileID string `json:"contract_file_id" gorm:"type:varchar(100);index"` // 合同文件ID
|
||||
SignURL string `json:"sign_url" gorm:"type:varchar(500)"` // 签署链接
|
||||
SignShortURL string `json:"sign_short_url" gorm:"type:varchar(500)"` // 签署短链接
|
||||
SignedFileURL string `json:"signed_file_url" gorm:"type:varchar(500)"` // 已签署文件URL
|
||||
|
||||
// 签署状态
|
||||
Status string `json:"status" gorm:"type:varchar(20);not null;default:'pending'"` // pending, signing, success, failed, expired
|
||||
RequestAt time.Time `json:"request_at" gorm:"not null"` // 申请签署时间
|
||||
SignedAt *time.Time `json:"signed_at"` // 签署完成时间
|
||||
ExpiredAt *time.Time `json:"expired_at"` // 签署链接过期时间
|
||||
FailedAt *time.Time `json:"failed_at"` // 失败时间
|
||||
FailureReason string `json:"failure_reason" gorm:"type:text"` // 失败原因
|
||||
|
||||
// 签署人信息
|
||||
SignerName string `json:"signer_name" gorm:"type:varchar(50)"` // 签署人姓名
|
||||
SignerPhone string `json:"signer_phone" gorm:"type:varchar(20)"` // 签署人手机号
|
||||
SignerIDCard string `json:"signer_id_card" gorm:"type:varchar(20)"` // 签署人身份证号
|
||||
|
||||
// 系统字段
|
||||
CreatedAt time.Time `json:"created_at" gorm:"not null"`
|
||||
UpdatedAt time.Time `json:"updated_at" gorm:"not null"`
|
||||
DeletedAt gorm.DeletedAt `json:"deleted_at" gorm:"index"`
|
||||
}
|
||||
|
||||
// TableName 指定表名
|
||||
func (EsignContractSignRecord) TableName() string {
|
||||
return "esign_contract_sign_records"
|
||||
}
|
||||
|
||||
// NewEsignContractSignRecord 创建新的e签宝签署合同记录
|
||||
func NewEsignContractSignRecord(
|
||||
certificationID, userID, esignFlowID, contractFileID, signerName, signerPhone, signerIDCard, signURL, signShortURL string,
|
||||
) *EsignContractSignRecord {
|
||||
// 设置签署链接过期时间为7天后
|
||||
expiredAt := time.Now().AddDate(0, 0, 7)
|
||||
|
||||
return &EsignContractSignRecord{
|
||||
ID: uuid.New().String(),
|
||||
CertificationID: certificationID,
|
||||
UserID: userID,
|
||||
EsignFlowID: esignFlowID,
|
||||
ContractFileID: contractFileID,
|
||||
SignURL: signURL,
|
||||
SignShortURL: signShortURL,
|
||||
SignerName: signerName,
|
||||
SignerPhone: signerPhone,
|
||||
SignerIDCard: signerIDCard,
|
||||
Status: "pending",
|
||||
RequestAt: time.Now(),
|
||||
ExpiredAt: &expiredAt,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsSigning 标记为签署中
|
||||
func (r *EsignContractSignRecord) MarkAsSigning() {
|
||||
r.Status = "signing"
|
||||
r.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// MarkAsSuccess 标记为签署成功
|
||||
func (r *EsignContractSignRecord) MarkAsSuccess(signedFileURL string) {
|
||||
now := time.Now()
|
||||
r.Status = "success"
|
||||
r.SignedFileURL = signedFileURL
|
||||
r.SignedAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsFailed 标记为签署失败
|
||||
func (r *EsignContractSignRecord) MarkAsFailed(reason string) {
|
||||
now := time.Now()
|
||||
r.Status = "failed"
|
||||
r.FailedAt = &now
|
||||
r.FailureReason = reason
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// MarkAsExpired 标记为已过期
|
||||
func (r *EsignContractSignRecord) MarkAsExpired() {
|
||||
now := time.Now()
|
||||
r.Status = "expired"
|
||||
r.ExpiredAt = &now
|
||||
r.UpdatedAt = now
|
||||
}
|
||||
|
||||
// SetSignURL 设置签署链接
|
||||
func (r *EsignContractSignRecord) SetSignURL(signURL string) {
|
||||
r.SignURL = signURL
|
||||
r.UpdatedAt = time.Now()
|
||||
}
|
||||
|
||||
// IsSuccess 检查是否签署成功
|
||||
func (r *EsignContractSignRecord) IsSuccess() bool {
|
||||
return r.Status == "success"
|
||||
}
|
||||
|
||||
// IsFailed 检查是否签署失败
|
||||
func (r *EsignContractSignRecord) IsFailed() bool {
|
||||
return r.Status == "failed"
|
||||
}
|
||||
|
||||
// IsExpired 检查是否已过期
|
||||
func (r *EsignContractSignRecord) IsExpired() bool {
|
||||
return r.Status == "expired" || (r.ExpiredAt != nil && time.Now().After(*r.ExpiredAt))
|
||||
}
|
||||
|
||||
// IsPending 检查是否待处理
|
||||
func (r *EsignContractSignRecord) IsPending() bool {
|
||||
return r.Status == "pending"
|
||||
}
|
||||
|
||||
// IsSigning 检查是否签署中
|
||||
func (r *EsignContractSignRecord) IsSigning() bool {
|
||||
return r.Status == "signing"
|
||||
}
|
||||
|
||||
// GetRemainingTime 获取剩余签署时间
|
||||
func (r *EsignContractSignRecord) GetRemainingTime() time.Duration {
|
||||
if r.ExpiredAt == nil {
|
||||
return 0
|
||||
}
|
||||
remaining := time.Until(*r.ExpiredAt)
|
||||
if remaining < 0 {
|
||||
return 0
|
||||
}
|
||||
return remaining
|
||||
}
|
||||
@@ -0,0 +1,516 @@
|
||||
package value_objects
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// ContractInfo 合同信息值对象
|
||||
// 封装电子合同相关的核心信息,包含合同状态和签署流程管理
|
||||
type ContractInfo struct {
|
||||
// 合同基本信息
|
||||
ContractFileID string `json:"contract_file_id"` // 合同文件ID
|
||||
EsignFlowID string `json:"esign_flow_id"` // e签宝签署流程ID
|
||||
ContractURL string `json:"contract_url"` // 合同文件访问链接
|
||||
ContractSignURL string `json:"contract_sign_url"` // 合同签署链接
|
||||
|
||||
// 合同元数据
|
||||
ContractTitle string `json:"contract_title"` // 合同标题
|
||||
ContractVersion string `json:"contract_version"` // 合同版本
|
||||
TemplateID string `json:"template_id"` // 模板ID
|
||||
|
||||
// 签署相关信息
|
||||
SignerAccount string `json:"signer_account"` // 签署人账号
|
||||
SignerName string `json:"signer_name"` // 签署人姓名
|
||||
TransactorPhone string `json:"transactor_phone"` // 经办人手机号
|
||||
TransactorName string `json:"transactor_name"` // 经办人姓名
|
||||
TransactorIDCardNum string `json:"transactor_id_card_num"` // 经办人身份证号
|
||||
|
||||
// 时间信息
|
||||
GeneratedAt *time.Time `json:"generated_at,omitempty"` // 合同生成时间
|
||||
SignFlowCreatedAt *time.Time `json:"sign_flow_created_at,omitempty"` // 签署流程创建时间
|
||||
SignedAt *time.Time `json:"signed_at,omitempty"` // 签署完成时间
|
||||
ExpiresAt *time.Time `json:"expires_at,omitempty"` // 签署链接过期时间
|
||||
|
||||
// 状态信息
|
||||
Status string `json:"status"` // 合同状态
|
||||
SignProgress int `json:"sign_progress"` // 签署进度
|
||||
|
||||
// 附加信息
|
||||
Metadata map[string]interface{} `json:"metadata,omitempty"` // 元数据
|
||||
}
|
||||
|
||||
// ContractStatus 合同状态常量
|
||||
const (
|
||||
ContractStatusDraft = "draft" // 草稿
|
||||
ContractStatusGenerated = "generated" // 已生成
|
||||
ContractStatusSigning = "signing" // 签署中
|
||||
ContractStatusSigned = "signed" // 已签署
|
||||
ContractStatusExpired = "expired" // 已过期
|
||||
ContractStatusRejected = "rejected" // 被拒绝
|
||||
ContractStatusCancelled = "cancelled" // 已取消
|
||||
)
|
||||
|
||||
// NewContractInfo 创建合同信息值对象
|
||||
func NewContractInfo(contractFileID, esignFlowID, contractURL, contractSignURL string) (*ContractInfo, error) {
|
||||
info := &ContractInfo{
|
||||
ContractFileID: strings.TrimSpace(contractFileID),
|
||||
EsignFlowID: strings.TrimSpace(esignFlowID),
|
||||
ContractURL: strings.TrimSpace(contractURL),
|
||||
ContractSignURL: strings.TrimSpace(contractSignURL),
|
||||
Status: ContractStatusGenerated,
|
||||
SignProgress: 0,
|
||||
Metadata: make(map[string]interface{}),
|
||||
}
|
||||
|
||||
if err := info.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("合同信息验证失败: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Validate 验证合同信息的完整性和格式
|
||||
func (c *ContractInfo) Validate() error {
|
||||
if err := c.validateContractFileID(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.validateEsignFlowID(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.validateContractURL(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.validateContractSignURL(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.validateSignerInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := c.validateStatus(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateContractFileID 验证合同文件ID
|
||||
func (c *ContractInfo) validateContractFileID() error {
|
||||
if c.ContractFileID == "" {
|
||||
return errors.New("合同文件ID不能为空")
|
||||
}
|
||||
|
||||
// 简单的格式验证
|
||||
if len(c.ContractFileID) < 10 {
|
||||
return errors.New("合同文件ID格式不正确")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEsignFlowID 验证e签宝流程ID
|
||||
func (c *ContractInfo) validateEsignFlowID() error {
|
||||
if c.EsignFlowID == "" {
|
||||
return errors.New("e签宝流程ID不能为空")
|
||||
}
|
||||
|
||||
// 简单的格式验证
|
||||
if len(c.EsignFlowID) < 10 {
|
||||
return errors.New("e签宝流程ID格式不正确")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateContractURL 验证合同访问链接
|
||||
func (c *ContractInfo) validateContractURL() error {
|
||||
if c.ContractURL == "" {
|
||||
return errors.New("合同访问链接不能为空")
|
||||
}
|
||||
|
||||
// URL格式验证
|
||||
urlPattern := `^https?://.*`
|
||||
matched, err := regexp.MatchString(urlPattern, c.ContractURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("合同访问链接格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("合同访问链接格式不正确,必须以http://或https://开头")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateContractSignURL 验证合同签署链接
|
||||
func (c *ContractInfo) validateContractSignURL() error {
|
||||
if c.ContractSignURL == "" {
|
||||
return errors.New("合同签署链接不能为空")
|
||||
}
|
||||
|
||||
// URL格式验证
|
||||
urlPattern := `^https?://.*`
|
||||
matched, err := regexp.MatchString(urlPattern, c.ContractSignURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("合同签署链接格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("合同签署链接格式不正确,必须以http://或https://开头")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateSignerInfo 验证签署人信息
|
||||
func (c *ContractInfo) validateSignerInfo() error {
|
||||
// 如果有签署人信息,进行验证
|
||||
if c.SignerAccount != "" || c.SignerName != "" || c.TransactorPhone != "" {
|
||||
if c.SignerAccount == "" {
|
||||
return errors.New("签署人账号不能为空")
|
||||
}
|
||||
|
||||
if c.SignerName == "" {
|
||||
return errors.New("签署人姓名不能为空")
|
||||
}
|
||||
|
||||
if c.TransactorPhone != "" {
|
||||
// 手机号格式验证
|
||||
phonePattern := `^1[3-9]\d{9}$`
|
||||
matched, err := regexp.MatchString(phonePattern, c.TransactorPhone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("经办人手机号格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("经办人手机号格式不正确")
|
||||
}
|
||||
}
|
||||
|
||||
if c.TransactorIDCardNum != "" {
|
||||
// 身份证号格式验证
|
||||
idPattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
|
||||
matched, err := regexp.MatchString(idPattern, c.TransactorIDCardNum)
|
||||
if err != nil {
|
||||
return fmt.Errorf("经办人身份证号格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("经办人身份证号格式不正确")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateStatus 验证合同状态
|
||||
func (c *ContractInfo) validateStatus() error {
|
||||
validStatuses := []string{
|
||||
ContractStatusDraft,
|
||||
ContractStatusGenerated,
|
||||
ContractStatusSigning,
|
||||
ContractStatusSigned,
|
||||
ContractStatusExpired,
|
||||
ContractStatusRejected,
|
||||
ContractStatusCancelled,
|
||||
}
|
||||
|
||||
for _, status := range validStatuses {
|
||||
if c.Status == status {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return fmt.Errorf("无效的合同状态: %s", c.Status)
|
||||
}
|
||||
|
||||
// SetSignerInfo 设置签署人信息
|
||||
func (c *ContractInfo) SetSignerInfo(signerAccount, signerName, transactorPhone, transactorName, transactorIDCardNum string) error {
|
||||
c.SignerAccount = strings.TrimSpace(signerAccount)
|
||||
c.SignerName = strings.TrimSpace(signerName)
|
||||
c.TransactorPhone = strings.TrimSpace(transactorPhone)
|
||||
c.TransactorName = strings.TrimSpace(transactorName)
|
||||
c.TransactorIDCardNum = strings.TrimSpace(transactorIDCardNum)
|
||||
|
||||
return c.validateSignerInfo()
|
||||
}
|
||||
|
||||
// UpdateStatus 更新合同状态
|
||||
func (c *ContractInfo) UpdateStatus(status string) error {
|
||||
oldStatus := c.Status
|
||||
c.Status = status
|
||||
|
||||
if err := c.validateStatus(); err != nil {
|
||||
c.Status = oldStatus // 回滚
|
||||
return err
|
||||
}
|
||||
|
||||
// 根据状态更新进度
|
||||
c.updateProgressByStatus()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateProgressByStatus 根据状态更新进度
|
||||
func (c *ContractInfo) updateProgressByStatus() {
|
||||
progressMap := map[string]int{
|
||||
ContractStatusDraft: 0,
|
||||
ContractStatusGenerated: 25,
|
||||
ContractStatusSigning: 50,
|
||||
ContractStatusSigned: 100,
|
||||
ContractStatusExpired: 50,
|
||||
ContractStatusRejected: 50,
|
||||
ContractStatusCancelled: 0,
|
||||
}
|
||||
|
||||
if progress, exists := progressMap[c.Status]; exists {
|
||||
c.SignProgress = progress
|
||||
}
|
||||
}
|
||||
|
||||
// MarkAsSigning 标记为签署中
|
||||
func (c *ContractInfo) MarkAsSigning() error {
|
||||
c.Status = ContractStatusSigning
|
||||
c.SignProgress = 50
|
||||
now := time.Now()
|
||||
c.SignFlowCreatedAt = &now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAsSigned 标记为已签署
|
||||
func (c *ContractInfo) MarkAsSigned() error {
|
||||
c.Status = ContractStatusSigned
|
||||
c.SignProgress = 100
|
||||
now := time.Now()
|
||||
c.SignedAt = &now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAsExpired 标记为已过期
|
||||
func (c *ContractInfo) MarkAsExpired() error {
|
||||
c.Status = ContractStatusExpired
|
||||
now := time.Now()
|
||||
c.ExpiresAt = &now
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarkAsRejected 标记为被拒绝
|
||||
func (c *ContractInfo) MarkAsRejected() error {
|
||||
c.Status = ContractStatusRejected
|
||||
c.SignProgress = 50
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsExpired 检查合同是否已过期
|
||||
func (c *ContractInfo) IsExpired() bool {
|
||||
if c.ExpiresAt == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return time.Now().After(*c.ExpiresAt)
|
||||
}
|
||||
|
||||
// IsSigned 检查合同是否已签署
|
||||
func (c *ContractInfo) IsSigned() bool {
|
||||
return c.Status == ContractStatusSigned
|
||||
}
|
||||
|
||||
// CanSign 检查是否可以签署
|
||||
func (c *ContractInfo) CanSign() bool {
|
||||
return c.Status == ContractStatusGenerated || c.Status == ContractStatusSigning
|
||||
}
|
||||
|
||||
// GetStatusName 获取状态的中文名称
|
||||
func (c *ContractInfo) GetStatusName() string {
|
||||
statusNames := map[string]string{
|
||||
ContractStatusDraft: "草稿",
|
||||
ContractStatusGenerated: "已生成",
|
||||
ContractStatusSigning: "签署中",
|
||||
ContractStatusSigned: "已签署",
|
||||
ContractStatusExpired: "已过期",
|
||||
ContractStatusRejected: "被拒绝",
|
||||
ContractStatusCancelled: "已取消",
|
||||
}
|
||||
|
||||
if name, exists := statusNames[c.Status]; exists {
|
||||
return name
|
||||
}
|
||||
return c.Status
|
||||
}
|
||||
|
||||
// GetDisplayTitle 获取显示用的合同标题
|
||||
func (c *ContractInfo) GetDisplayTitle() string {
|
||||
if c.ContractTitle != "" {
|
||||
return c.ContractTitle
|
||||
}
|
||||
return "企业认证服务合同"
|
||||
}
|
||||
|
||||
// GetMaskedSignerAccount 获取脱敏的签署人账号
|
||||
func (c *ContractInfo) GetMaskedSignerAccount() string {
|
||||
if len(c.SignerAccount) <= 6 {
|
||||
return c.SignerAccount
|
||||
}
|
||||
|
||||
// 保留前3位和后3位,中间用*替代
|
||||
return c.SignerAccount[:3] + "***" + c.SignerAccount[len(c.SignerAccount)-3:]
|
||||
}
|
||||
|
||||
// GetMaskedTransactorPhone 获取脱敏的经办人手机号
|
||||
func (c *ContractInfo) GetMaskedTransactorPhone() string {
|
||||
if len(c.TransactorPhone) != 11 {
|
||||
return c.TransactorPhone
|
||||
}
|
||||
|
||||
// 保留前3位和后4位,中间用*替代
|
||||
return c.TransactorPhone[:3] + "****" + c.TransactorPhone[7:]
|
||||
}
|
||||
|
||||
// GetMaskedTransactorIDCardNum 获取脱敏的经办人身份证号
|
||||
func (c *ContractInfo) GetMaskedTransactorIDCardNum() string {
|
||||
if len(c.TransactorIDCardNum) != 18 {
|
||||
return c.TransactorIDCardNum
|
||||
}
|
||||
|
||||
// 保留前6位和后4位,中间用*替代
|
||||
return c.TransactorIDCardNum[:6] + "********" + c.TransactorIDCardNum[14:]
|
||||
}
|
||||
|
||||
// AddMetadata 添加元数据
|
||||
func (c *ContractInfo) AddMetadata(key string, value interface{}) {
|
||||
if c.Metadata == nil {
|
||||
c.Metadata = make(map[string]interface{})
|
||||
}
|
||||
c.Metadata[key] = value
|
||||
}
|
||||
|
||||
// GetMetadata 获取元数据
|
||||
func (c *ContractInfo) GetMetadata(key string) (interface{}, bool) {
|
||||
if c.Metadata == nil {
|
||||
return nil, false
|
||||
}
|
||||
value, exists := c.Metadata[key]
|
||||
return value, exists
|
||||
}
|
||||
|
||||
// Equals 比较两个合同信息是否相等
|
||||
func (c *ContractInfo) Equals(other *ContractInfo) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return c.ContractFileID == other.ContractFileID &&
|
||||
c.EsignFlowID == other.EsignFlowID &&
|
||||
c.Status == other.Status
|
||||
}
|
||||
|
||||
// Clone 创建合同信息的副本
|
||||
func (c *ContractInfo) Clone() *ContractInfo {
|
||||
cloned := &ContractInfo{
|
||||
ContractFileID: c.ContractFileID,
|
||||
EsignFlowID: c.EsignFlowID,
|
||||
ContractURL: c.ContractURL,
|
||||
ContractSignURL: c.ContractSignURL,
|
||||
ContractTitle: c.ContractTitle,
|
||||
ContractVersion: c.ContractVersion,
|
||||
TemplateID: c.TemplateID,
|
||||
SignerAccount: c.SignerAccount,
|
||||
SignerName: c.SignerName,
|
||||
TransactorPhone: c.TransactorPhone,
|
||||
TransactorName: c.TransactorName,
|
||||
TransactorIDCardNum: c.TransactorIDCardNum,
|
||||
Status: c.Status,
|
||||
SignProgress: c.SignProgress,
|
||||
}
|
||||
|
||||
// 复制时间字段
|
||||
if c.GeneratedAt != nil {
|
||||
generatedAt := *c.GeneratedAt
|
||||
cloned.GeneratedAt = &generatedAt
|
||||
}
|
||||
if c.SignFlowCreatedAt != nil {
|
||||
signFlowCreatedAt := *c.SignFlowCreatedAt
|
||||
cloned.SignFlowCreatedAt = &signFlowCreatedAt
|
||||
}
|
||||
if c.SignedAt != nil {
|
||||
signedAt := *c.SignedAt
|
||||
cloned.SignedAt = &signedAt
|
||||
}
|
||||
if c.ExpiresAt != nil {
|
||||
expiresAt := *c.ExpiresAt
|
||||
cloned.ExpiresAt = &expiresAt
|
||||
}
|
||||
|
||||
// 复制元数据
|
||||
if c.Metadata != nil {
|
||||
cloned.Metadata = make(map[string]interface{})
|
||||
for k, v := range c.Metadata {
|
||||
cloned.Metadata[k] = v
|
||||
}
|
||||
}
|
||||
|
||||
return cloned
|
||||
}
|
||||
|
||||
// String 返回合同信息的字符串表示
|
||||
func (c *ContractInfo) String() string {
|
||||
return fmt.Sprintf("合同信息[文件ID:%s, 流程ID:%s, 状态:%s, 进度:%d%%]",
|
||||
c.ContractFileID,
|
||||
c.EsignFlowID,
|
||||
c.GetStatusName(),
|
||||
c.SignProgress)
|
||||
}
|
||||
|
||||
// ToMap 转换为map格式(用于序列化)
|
||||
func (c *ContractInfo) ToMap() map[string]interface{} {
|
||||
result := map[string]interface{}{
|
||||
"contract_file_id": c.ContractFileID,
|
||||
"esign_flow_id": c.EsignFlowID,
|
||||
"contract_url": c.ContractURL,
|
||||
"contract_sign_url": c.ContractSignURL,
|
||||
"contract_title": c.ContractTitle,
|
||||
"contract_version": c.ContractVersion,
|
||||
"template_id": c.TemplateID,
|
||||
"signer_account": c.SignerAccount,
|
||||
"signer_name": c.SignerName,
|
||||
"transactor_phone": c.TransactorPhone,
|
||||
"transactor_name": c.TransactorName,
|
||||
"transactor_id_card_num": c.TransactorIDCardNum,
|
||||
"status": c.Status,
|
||||
"sign_progress": c.SignProgress,
|
||||
}
|
||||
|
||||
// 添加时间字段
|
||||
if c.GeneratedAt != nil {
|
||||
result["generated_at"] = c.GeneratedAt
|
||||
}
|
||||
if c.SignFlowCreatedAt != nil {
|
||||
result["sign_flow_created_at"] = c.SignFlowCreatedAt
|
||||
}
|
||||
if c.SignedAt != nil {
|
||||
result["signed_at"] = c.SignedAt
|
||||
}
|
||||
if c.ExpiresAt != nil {
|
||||
result["expires_at"] = c.ExpiresAt
|
||||
}
|
||||
|
||||
// 添加元数据
|
||||
if c.Metadata != nil {
|
||||
result["metadata"] = c.Metadata
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
@@ -0,0 +1,365 @@
|
||||
package value_objects
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"regexp"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// EnterpriseInfo 企业信息值对象
|
||||
// 封装企业认证所需的核心信息,包含完整的业务规则验证
|
||||
type EnterpriseInfo struct {
|
||||
// 企业基本信息
|
||||
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"` // 法定代表人手机号
|
||||
|
||||
// 企业详细信息
|
||||
RegisteredAddress string `json:"registered_address"` // 注册地址
|
||||
EnterpriseAddress string `json:"enterprise_address"` // 企业地址(新增)
|
||||
}
|
||||
|
||||
// NewEnterpriseInfo 创建企业信息值对象
|
||||
func NewEnterpriseInfo(companyName, unifiedSocialCode, legalPersonName, legalPersonID, legalPersonPhone, enterpriseAddress string) (*EnterpriseInfo, error) {
|
||||
info := &EnterpriseInfo{
|
||||
CompanyName: strings.TrimSpace(companyName),
|
||||
UnifiedSocialCode: strings.TrimSpace(unifiedSocialCode),
|
||||
LegalPersonName: strings.TrimSpace(legalPersonName),
|
||||
LegalPersonID: strings.TrimSpace(legalPersonID),
|
||||
LegalPersonPhone: strings.TrimSpace(legalPersonPhone),
|
||||
EnterpriseAddress: strings.TrimSpace(enterpriseAddress),
|
||||
}
|
||||
|
||||
if err := info.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("企业信息验证失败: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
|
||||
// Validate 验证企业信息的完整性和格式
|
||||
func (e *EnterpriseInfo) Validate() error {
|
||||
if err := e.validateCompanyName(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.validateUnifiedSocialCode(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.validateLegalPersonName(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.validateLegalPersonID(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.validateLegalPersonPhone(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if err := e.validateEnterpriseAddress(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateCompanyName 验证企业名称
|
||||
func (e *EnterpriseInfo) validateCompanyName() error {
|
||||
if e.CompanyName == "" {
|
||||
return errors.New("企业名称不能为空")
|
||||
}
|
||||
|
||||
if len(e.CompanyName) < 2 {
|
||||
return errors.New("企业名称长度不能少于2个字符")
|
||||
}
|
||||
|
||||
if len(e.CompanyName) > 100 {
|
||||
return errors.New("企业名称长度不能超过100个字符")
|
||||
}
|
||||
|
||||
// 检查是否包含非法字符(允许括号)
|
||||
invalidChars := []string{
|
||||
"`", "~", "!", "@", "#", "$", "%", "^", "&", "*",
|
||||
"+", "=", "{", "}", "[", "]", "【", "】", "\\", "|", ";", ":", "'", "\"", "<", ">", ",", ".", "?", "/",
|
||||
}
|
||||
for _, char := range invalidChars {
|
||||
if strings.Contains(e.CompanyName, char) {
|
||||
return fmt.Errorf("企业名称不能包含特殊字符: %s", char)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateUnifiedSocialCode 验证统一社会信用代码
|
||||
func (e *EnterpriseInfo) validateUnifiedSocialCode() error {
|
||||
if e.UnifiedSocialCode == "" {
|
||||
return errors.New("统一社会信用代码不能为空")
|
||||
}
|
||||
|
||||
// 统一社会信用代码格式验证(18位数字和字母)
|
||||
pattern := `^[0-9A-HJ-NPQRTUWXY]{2}[0-9]{6}[0-9A-HJ-NPQRTUWXY]{10}$`
|
||||
matched, err := regexp.MatchString(pattern, e.UnifiedSocialCode)
|
||||
if err != nil {
|
||||
return fmt.Errorf("统一社会信用代码格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("统一社会信用代码格式不正确,应为18位数字和字母组合")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLegalPersonName 验证法定代表人姓名
|
||||
func (e *EnterpriseInfo) validateLegalPersonName() error {
|
||||
if e.LegalPersonName == "" {
|
||||
return errors.New("法定代表人姓名不能为空")
|
||||
}
|
||||
|
||||
if len(e.LegalPersonName) < 2 {
|
||||
return errors.New("法定代表人姓名长度不能少于2个字符")
|
||||
}
|
||||
|
||||
if len(e.LegalPersonName) > 50 {
|
||||
return errors.New("法定代表人姓名长度不能超过50个字符")
|
||||
}
|
||||
|
||||
// 中文姓名格式验证
|
||||
pattern := "^[一-龥·]+$"
|
||||
matched, err := regexp.MatchString(pattern, e.LegalPersonName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("法定代表人姓名格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("法定代表人姓名只能包含中文字符和间隔号")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateLegalPersonID 验证法定代表人身份证号
|
||||
func (e *EnterpriseInfo) validateLegalPersonID() error {
|
||||
if e.LegalPersonID == "" {
|
||||
return errors.New("法定代表人身份证号不能为空")
|
||||
}
|
||||
|
||||
// 身份证号格式验证(18位)
|
||||
if len(e.LegalPersonID) != 18 {
|
||||
return errors.New("身份证号必须为18位")
|
||||
}
|
||||
|
||||
pattern := `^[1-9]\d{5}(19|20)\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])\d{3}[\dXx]$`
|
||||
matched, err := regexp.MatchString(pattern, e.LegalPersonID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("身份证号格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("身份证号格式不正确")
|
||||
}
|
||||
|
||||
// 身份证号校验码验证
|
||||
if !e.validateIDChecksum() {
|
||||
return errors.New("身份证号校验码错误")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateIDChecksum 验证身份证号校验码
|
||||
func (e *EnterpriseInfo) validateIDChecksum() bool {
|
||||
if len(e.LegalPersonID) != 18 {
|
||||
return false
|
||||
}
|
||||
|
||||
// 加权因子
|
||||
weights := []int{7, 9, 10, 5, 8, 4, 2, 1, 6, 3, 7, 9, 10, 5, 8, 4, 2}
|
||||
// 校验码对应表
|
||||
checkCodes := []string{"1", "0", "X", "9", "8", "7", "6", "5", "4", "3", "2"}
|
||||
|
||||
sum := 0
|
||||
for i := 0; i < 17; i++ {
|
||||
digit := int(e.LegalPersonID[i] - '0')
|
||||
sum += digit * weights[i]
|
||||
}
|
||||
|
||||
checkCodeIndex := sum % 11
|
||||
expectedCheckCode := checkCodes[checkCodeIndex]
|
||||
actualCheckCode := strings.ToUpper(string(e.LegalPersonID[17]))
|
||||
|
||||
return expectedCheckCode == actualCheckCode
|
||||
}
|
||||
|
||||
// validateLegalPersonPhone 验证法定代表人手机号
|
||||
func (e *EnterpriseInfo) validateLegalPersonPhone() error {
|
||||
if e.LegalPersonPhone == "" {
|
||||
return errors.New("法定代表人手机号不能为空")
|
||||
}
|
||||
|
||||
// 手机号格式验证(11位数字,1开头)
|
||||
pattern := `^1[3-9]\d{9}$`
|
||||
matched, err := regexp.MatchString(pattern, e.LegalPersonPhone)
|
||||
if err != nil {
|
||||
return fmt.Errorf("手机号格式验证错误: %w", err)
|
||||
}
|
||||
|
||||
if !matched {
|
||||
return errors.New("手机号格式不正确,应为11位数字且以1开头")
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// validateEnterpriseAddress 验证企业地址
|
||||
func (e *EnterpriseInfo) validateEnterpriseAddress() error {
|
||||
if strings.TrimSpace(e.EnterpriseAddress) == "" {
|
||||
return errors.New("企业地址不能为空")
|
||||
}
|
||||
if len(e.EnterpriseAddress) < 5 {
|
||||
return errors.New("企业地址长度不能少于5个字符")
|
||||
}
|
||||
if len(e.EnterpriseAddress) > 200 {
|
||||
return errors.New("企业地址长度不能超过200个字符")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// IsComplete 检查企业信息是否完整
|
||||
func (e *EnterpriseInfo) IsComplete() bool {
|
||||
return e.CompanyName != "" &&
|
||||
e.UnifiedSocialCode != "" &&
|
||||
e.LegalPersonName != "" &&
|
||||
e.LegalPersonID != "" &&
|
||||
e.LegalPersonPhone != "" &&
|
||||
e.EnterpriseAddress != ""
|
||||
}
|
||||
|
||||
// IsDetailComplete 检查企业详细信息是否完整
|
||||
func (e *EnterpriseInfo) IsDetailComplete() bool {
|
||||
return e.IsComplete() &&
|
||||
e.RegisteredAddress != "" &&
|
||||
e.EnterpriseAddress != ""
|
||||
}
|
||||
|
||||
// GetDisplayName 获取显示用的企业名称
|
||||
func (e *EnterpriseInfo) GetDisplayName() string {
|
||||
if e.CompanyName == "" {
|
||||
return "未知企业"
|
||||
}
|
||||
return e.CompanyName
|
||||
}
|
||||
|
||||
// GetMaskedUnifiedSocialCode 获取脱敏的统一社会信用代码
|
||||
func (e *EnterpriseInfo) GetMaskedUnifiedSocialCode() string {
|
||||
if len(e.UnifiedSocialCode) != 18 {
|
||||
return e.UnifiedSocialCode
|
||||
}
|
||||
|
||||
// 保留前6位和后4位,中间用*替代
|
||||
return e.UnifiedSocialCode[:6] + "********" + e.UnifiedSocialCode[14:]
|
||||
}
|
||||
|
||||
// GetMaskedLegalPersonID 获取脱敏的法定代表人身份证号
|
||||
func (e *EnterpriseInfo) GetMaskedLegalPersonID() string {
|
||||
if len(e.LegalPersonID) != 18 {
|
||||
return e.LegalPersonID
|
||||
}
|
||||
|
||||
// 保留前6位和后4位,中间用*替代
|
||||
return e.LegalPersonID[:6] + "********" + e.LegalPersonID[14:]
|
||||
}
|
||||
|
||||
// GetMaskedLegalPersonPhone 获取脱敏的法定代表人手机号
|
||||
func (e *EnterpriseInfo) GetMaskedLegalPersonPhone() string {
|
||||
if len(e.LegalPersonPhone) != 11 {
|
||||
return e.LegalPersonPhone
|
||||
}
|
||||
|
||||
// 保留前3位和后4位,中间用*替代
|
||||
return e.LegalPersonPhone[:3] + "****" + e.LegalPersonPhone[7:]
|
||||
}
|
||||
|
||||
// Equals 比较两个企业信息是否相等
|
||||
func (e *EnterpriseInfo) Equals(other *EnterpriseInfo) bool {
|
||||
if other == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
return e.CompanyName == other.CompanyName &&
|
||||
e.UnifiedSocialCode == other.UnifiedSocialCode &&
|
||||
e.LegalPersonName == other.LegalPersonName &&
|
||||
e.LegalPersonID == other.LegalPersonID &&
|
||||
e.LegalPersonPhone == other.LegalPersonPhone
|
||||
}
|
||||
|
||||
// Clone 创建企业信息的副本
|
||||
func (e *EnterpriseInfo) Clone() *EnterpriseInfo {
|
||||
return &EnterpriseInfo{
|
||||
CompanyName: e.CompanyName,
|
||||
UnifiedSocialCode: e.UnifiedSocialCode,
|
||||
LegalPersonName: e.LegalPersonName,
|
||||
LegalPersonID: e.LegalPersonID,
|
||||
LegalPersonPhone: e.LegalPersonPhone,
|
||||
RegisteredAddress: e.RegisteredAddress,
|
||||
EnterpriseAddress: e.EnterpriseAddress,
|
||||
}
|
||||
}
|
||||
|
||||
// String 返回企业信息的字符串表示
|
||||
func (e *EnterpriseInfo) String() string {
|
||||
return fmt.Sprintf("企业信息[名称:%s, 信用代码:%s, 法人:%s]",
|
||||
e.CompanyName,
|
||||
e.GetMaskedUnifiedSocialCode(),
|
||||
e.LegalPersonName)
|
||||
}
|
||||
|
||||
// ToMap 转换为map格式(用于序列化)
|
||||
func (e *EnterpriseInfo) ToMap() map[string]interface{} {
|
||||
return map[string]interface{}{
|
||||
"company_name": e.CompanyName,
|
||||
"unified_social_code": e.UnifiedSocialCode,
|
||||
"legal_person_name": e.LegalPersonName,
|
||||
"legal_person_id": e.LegalPersonID,
|
||||
"legal_person_phone": e.LegalPersonPhone,
|
||||
"registered_address": e.RegisteredAddress,
|
||||
"enterprise_address": e.EnterpriseAddress,
|
||||
}
|
||||
}
|
||||
|
||||
// FromMap 从map格式创建企业信息(用于反序列化)
|
||||
func FromMap(data map[string]interface{}) (*EnterpriseInfo, error) {
|
||||
getString := func(key string) string {
|
||||
if val, exists := data[key]; exists {
|
||||
if str, ok := val.(string); ok {
|
||||
return strings.TrimSpace(str)
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
info := &EnterpriseInfo{
|
||||
CompanyName: getString("company_name"),
|
||||
UnifiedSocialCode: getString("unified_social_code"),
|
||||
LegalPersonName: getString("legal_person_name"),
|
||||
LegalPersonID: getString("legal_person_id"),
|
||||
LegalPersonPhone: getString("legal_person_phone"),
|
||||
RegisteredAddress: getString("registered_address"),
|
||||
EnterpriseAddress: getString("enterprise_address"),
|
||||
}
|
||||
|
||||
if err := info.Validate(); err != nil {
|
||||
return nil, fmt.Errorf("从Map创建企业信息失败: %w", err)
|
||||
}
|
||||
|
||||
return info, nil
|
||||
}
|
||||
Reference in New Issue
Block a user