Files
tyapi-server/internal/domains/certification/entities/value_objects/contract_info.go

516 lines
14 KiB
Go
Raw Normal View History

2025-07-21 15:13:26 +08:00
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
}