package model import ( "errors" "fmt" "strconv" "strings" "time" ) const QRVersion = 1 // IdentityLink 不透明二维码映射,避免把内部 ID 或个人信息放进二维码。 type IdentityLink struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` PublicID string `gorm:"column:public_id;size:64;uniqueIndex" json:"publicId"` EntityType string `gorm:"column:entity_type;size:16;index" json:"entityType"` EntityID string `gorm:"column:entity_id;size:128;uniqueIndex" json:"entityId"` Version int `gorm:"default:1" json:"version"` CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"` UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"` } func (IdentityLink) TableName() string { return "identity_links" } // SeedSource 蚕种来源与检疫链。 type SeedSource struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` PublicID string `gorm:"column:public_id;size:64;uniqueIndex" json:"publicId"` BatchID *string `gorm:"column:batch_id;type:uuid;index" json:"batchId,omitempty"` ParentID *string `gorm:"column:parent_id;type:uuid;index" json:"parentId,omitempty"` Supplier string `gorm:"size:128" json:"supplier"` SeedBatchNo string `gorm:"column:seed_batch_no;size:64" json:"seedBatchNo"` QuarantineNo *string `gorm:"column:quarantine_no;size:64" json:"quarantineNo,omitempty"` Variety *string `gorm:"size:64" json:"variety,omitempty"` CertificateURL *string `gorm:"column:certificate_url;size:512" json:"certificateUrl,omitempty"` EntryAt *time.Time `gorm:"column:entry_at;type:timestamptz" json:"entryAt,omitempty"` Note *string `gorm:"type:text" json:"note,omitempty"` CreatedBy *string `gorm:"column:created_by;type:uuid" json:"createdBy,omitempty"` CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"` UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"` } func (SeedSource) TableName() string { return "seed_sources" } // DisinfectionRecord 消毒计划与执行记录。 type DisinfectionRecord struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` RoomID *string `gorm:"column:room_id;type:uuid;index" json:"roomId,omitempty"` BatchID *string `gorm:"column:batch_id;type:uuid;index" json:"batchId,omitempty"` Kind string `gorm:"size:16;default:plan" json:"kind"` // plan/execution PlanID *string `gorm:"column:plan_id;type:uuid;index" json:"planId,omitempty"` Agent string `gorm:"size:128" json:"agent"` Concentration string `gorm:"size:64" json:"concentration"` Amount *string `gorm:"size:64" json:"amount,omitempty"` PlannedAt *time.Time `gorm:"column:planned_at;type:timestamptz" json:"plannedAt,omitempty"` ExecutedAt *time.Time `gorm:"column:executed_at;type:timestamptz" json:"executedAt,omitempty"` ExecutorID *string `gorm:"column:executor_id;type:uuid" json:"executorId,omitempty"` ReviewedAt *time.Time `gorm:"column:reviewed_at;type:timestamptz" json:"reviewedAt,omitempty"` ReviewerID *string `gorm:"column:reviewer_id;type:uuid" json:"reviewerId,omitempty"` PhotoURL *string `gorm:"column:photo_url;size:512" json:"photoUrl,omitempty"` Note *string `gorm:"type:text" json:"note,omitempty"` CreatedBy *string `gorm:"column:created_by;type:uuid" json:"createdBy,omitempty"` CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"` UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"` } func (DisinfectionRecord) TableName() string { return "disinfection_records" } // EncodeQRPayload 生成不透明二维码载荷。 func EncodeQRPayload(entityType, publicID string, version int) string { return fmt.Sprintf("silk:v1:%s:%s:%d", entityType, publicID, version) } // ParseQRPayload 解析并校验二维码载荷。 func ParseQRPayload(payload string) (string, string, int, error) { parts := strings.Split(strings.TrimSpace(payload), ":") if len(parts) != 5 || parts[0] != "silk" || parts[1] != "v1" { return "", "", 0, errors.New("二维码格式不正确") } entityType := parts[2] publicID := parts[3] version, err := strconv.Atoi(parts[4]) if err != nil || version <= 0 { return "", "", 0, errors.New("二维码版本不正确") } if !ValidQRIdentityType(entityType) || publicID == "" { return "", "", 0, errors.New("二维码身份无效") } return entityType, publicID, version, nil } // ValidQRIdentityType 当前二维码支持的实体类型。 func ValidQRIdentityType(entityType string) bool { switch entityType { case "batch", "tray", "sample": return true default: return false } } // ValidateSeedSource 种源必填项。 func ValidateSeedSource(source SeedSource) error { if strings.TrimSpace(source.Supplier) == "" { return errors.New("供应商不能为空") } if strings.TrimSpace(source.SeedBatchNo) == "" { return errors.New("蚕种批号不能为空") } return nil } // ValidateDisinfectionRecord 消毒必填项。 func ValidateDisinfectionRecord(record DisinfectionRecord) error { if record.Kind != "plan" && record.Kind != "execution" { return errors.New("kind 仅支持 plan/execution") } if strings.TrimSpace(record.Agent) == "" { return errors.New("消毒药剂不能为空") } if strings.TrimSpace(record.Concentration) == "" { return errors.New("消毒浓度不能为空") } return nil } // SeedSourceCycleError 检测种源链是否形成循环。 func SeedSourceCycleError(chain map[string]string, id, parentID string) error { seen := map[string]bool{id: true} current := parentID for current != "" { if seen[current] { return errors.New("种源链不能形成循环") } seen[current] = true next := chain[current] if next == current { return errors.New("种源链不能自引用") } current = next } return nil }