package entities import ( "errors" "fmt" "time" "tyapi-server/internal/domains/certification/entities/value_objects" "tyapi-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 } // ================ 业务操作方法 ================ // 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 } // 完成企业认证 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 c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" { return errors.New("合同信息不完整,无法完成认证") } // 状态转换 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.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.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.StatusEnterpriseVerified: if c.ContractURL == "" { return errors.New("企业认证成功后,合同文件ID和合同URL不能为空") } case enums.StatusContractSigned: if c.ContractFileID == "" || c.EsignFlowID == "" { return errors.New("合同签署状态下必须有完整的合同信息") } case enums.StatusCompleted: if c.ContractFileID == "" || c.EsignFlowID == "" || c.ContractURL == "" { return errors.New("认证完成状态下必须有完整的合同信息") } 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.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"` }