package model import ( "encoding/json" "errors" "strings" "time" ) // Organization 组织/养殖场/合作社,用于对象级数据授权。 type Organization struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` Name string `gorm:"size:128" json:"name"` Code string `gorm:"size:64;uniqueIndex" json:"code"` Description *string `gorm:"type:text" json:"description,omitempty"` Status string `gorm:"size:16;default:active" json:"status"` CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"` UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"` } func (Organization) TableName() string { return "organizations" } // OrganizationMember 用户与组织归属关系。 type OrganizationMember struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` OrganizationID string `gorm:"column:organization_id;type:uuid;uniqueIndex:idx_org_members_org_user,priority:1" json:"organizationId"` UserID string `gorm:"column:user_id;type:uuid;uniqueIndex:idx_org_members_org_user,priority:2" json:"userId"` Role string `gorm:"size:16;default:member" json:"role"` CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"` } func (OrganizationMember) TableName() string { return "organization_members" } // DeviceMaintenanceRecord 设备校准/故障/维护/固件记录。 type DeviceMaintenanceRecord struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` DeviceID string `gorm:"column:device_id;type:uuid;index" json:"deviceId"` Kind string `gorm:"size:16;index" json:"kind"` // calibration/fault/maintenance/firmware Title string `gorm:"size:128" json:"title"` ScheduledAt *time.Time `gorm:"column:scheduled_at;type:timestamptz" json:"scheduledAt,omitempty"` PerformedAt *time.Time `gorm:"column:performed_at;type:timestamptz" json:"performedAt,omitempty"` PerformerID *string `gorm:"column:performer_id;type:uuid" json:"performerId,omitempty"` Result *string `gorm:"type:text" json:"result,omitempty"` FirmwareFrom *string `gorm:"column:firmware_from;size:64" json:"firmwareFrom,omitempty"` FirmwareTo *string `gorm:"column:firmware_to;size:64" json:"firmwareTo,omitempty"` CostAmount *float64 `gorm:"column:cost_amount;type:float" json:"costAmount,omitempty"` CostUnit *string `gorm:"column:cost_unit;size:16" json:"costUnit,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 (DeviceMaintenanceRecord) TableName() string { return "device_maintenance_records" } // ValidMaintenanceKind 设备维护记录类型。 func ValidMaintenanceKind(kind string) bool { switch kind { case "calibration", "fault", "maintenance", "firmware": return true default: return false } } // ValidateDeviceMaintenanceRecord 设备维护必填项。 func ValidateDeviceMaintenanceRecord(record DeviceMaintenanceRecord) error { if !ValidMaintenanceKind(record.Kind) { return errors.New("kind 仅支持 calibration/fault/maintenance/firmware") } if strings.TrimSpace(record.Title) == "" { return errors.New("维护标题不能为空") } return nil } // ProductionLossRecord 产量、死亡、淘汰、损失和防控成本记录。 type ProductionLossRecord 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"` RecordDate time.Time `gorm:"column:record_date;type:timestamptz;index" json:"recordDate"` DeathCount *int `gorm:"column:death_count;type:int" json:"deathCount,omitempty"` CulledCount *int `gorm:"column:culled_count;type:int" json:"culledCount,omitempty"` YieldKg *float64 `gorm:"column:yield_kg;type:float" json:"yieldKg,omitempty"` LossKg *float64 `gorm:"column:loss_kg;type:float" json:"lossKg,omitempty"` CostType *string `gorm:"column:cost_type;size:32" json:"costType,omitempty"` // medicine/disinfection/detection/labor/other CostAmount *float64 `gorm:"column:cost_amount;type:float" json:"costAmount,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 (ProductionLossRecord) TableName() string { return "production_loss_records" } // ValidateProductionLossRecord 产量损失记录必须至少有一个业务数值。 func ValidateProductionLossRecord(record ProductionLossRecord) error { if record.RoomID == nil && record.BatchID == nil { return errors.New("roomId/batchId 至少填一个") } if record.RecordDate.IsZero() { return errors.New("记录日期不能为空") } hasValue := record.DeathCount != nil || record.CulledCount != nil || record.YieldKg != nil || record.LossKg != nil || record.CostAmount != nil if !hasValue { return errors.New("死亡/淘汰/产量/损失/成本至少填写一项") } return nil } // CaseStudy 已脱敏病例沉淀,必须经审核后发布。 type CaseStudy struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` Title string `gorm:"size:128" json:"title"` Disease string `gorm:"size:64;index" json:"disease"` SourceConsultationID *string `gorm:"column:source_consultation_id;type:uuid;index" json:"sourceConsultationId,omitempty"` SourceDiseaseEventID *string `gorm:"column:source_disease_event_id;type:uuid;index" json:"sourceDiseaseEventId,omitempty"` SourceRoomID *string `gorm:"column:source_room_id;type:uuid;index" json:"sourceRoomId,omitempty"` Region *string `gorm:"size:64" json:"region,omitempty"` CaseDate *time.Time `gorm:"column:case_date;type:timestamptz" json:"caseDate,omitempty"` Summary *string `gorm:"type:text" json:"summary,omitempty"` DesensitizedPayload json.RawMessage `gorm:"column:desensitized_payload;type:jsonb" json:"desensitizedPayload,omitempty"` Status string `gorm:"size:16;default:draft;index" json:"status"` // draft/pending_review/published/rejected ReviewNote *string `gorm:"column:review_note;type:text" json:"reviewNote,omitempty"` ReviewerID *string `gorm:"column:reviewer_id;type:uuid" json:"reviewerId,omitempty"` ReviewedAt *time.Time `gorm:"column:reviewed_at;type:timestamptz" json:"reviewedAt,omitempty"` PublishedAt *time.Time `gorm:"column:published_at;type:timestamptz" json:"publishedAt,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 (CaseStudy) TableName() string { return "case_studies" } // ValidCaseStudyTransition 案例审核状态流转。 func ValidCaseStudyTransition(from, to string) bool { switch from { case "draft": return to == "pending_review" || to == "rejected" case "pending_review": return to == "published" || to == "rejected" case "published": return to == "rejected" || to == "pending_review" case "rejected": return to == "draft" || to == "pending_review" default: return false } } // LaboratoryResult 实验室结构化结果,关联三级溯源、发病事件和样本。 type LaboratoryResult struct { ID string `gorm:"type:uuid;primaryKey;default:gen_random_uuid()" json:"id"` TraceRecordID *string `gorm:"column:trace_record_id;type:uuid;index" json:"traceRecordId,omitempty"` DiseaseEventID *string `gorm:"column:disease_event_id;type:uuid;index" json:"diseaseEventId,omitempty"` SampleID *string `gorm:"column:sample_id;type:uuid;index" json:"sampleId,omitempty"` RoomID *string `gorm:"column:room_id;type:uuid;index" json:"roomId,omitempty"` BatchID *string `gorm:"column:batch_id;type:uuid;index" json:"batchId,omitempty"` LabName string `gorm:"column:lab_name;size:128" json:"labName"` ReportNo string `gorm:"column:report_no;size:64" json:"reportNo"` TestType string `gorm:"column:test_type;size:32" json:"testType"` // molecular_typing/pathogen/environment_sample/other ResultType string `gorm:"column:result_type;size:16" json:"resultType"` Pathogen *string `gorm:"size:128" json:"pathogen,omitempty"` Genotype *string `gorm:"size:128" json:"genotype,omitempty"` Method *string `gorm:"size:128" json:"method,omitempty"` SampleNo *string `gorm:"column:sample_no;size:64" json:"sampleNo,omitempty"` SampleType *string `gorm:"column:sample_type;size:32" json:"sampleType,omitempty"` Findings *string `gorm:"type:text" json:"findings,omitempty"` ReportURL *string `gorm:"column:report_url;size:512" json:"reportUrl,omitempty"` TestedAt *time.Time `gorm:"column:tested_at;type:timestamptz" json:"testedAt,omitempty"` ConcludedAt *time.Time `gorm:"column:concluded_at;type:timestamptz" json:"concludedAt,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 (LaboratoryResult) TableName() string { return "laboratory_results" } // ValidateLaboratoryResult 实验室结果必填项。 func ValidateLaboratoryResult(result LaboratoryResult) error { if strings.TrimSpace(result.LabName) == "" { return errors.New("实验室名称不能为空") } if strings.TrimSpace(result.ReportNo) == "" { return errors.New("报告编号不能为空") } if !ValidLaboratoryTestType(result.TestType) { return errors.New("testType 仅支持 molecular_typing/pathogen/environment_sample/other") } if !ValidLaboratoryResultType(result.ResultType) { return errors.New("resultType 仅支持 positive/negative/indeterminate/invalid") } return nil } // ValidLaboratoryTestType 实验室检测类型。 func ValidLaboratoryTestType(testType string) bool { switch testType { case "molecular_typing", "pathogen", "environment_sample", "other": return true default: return false } } // ValidLaboratoryResultType 实验室结果类型。 func ValidLaboratoryResultType(resultType string) bool { switch resultType { case "positive", "negative", "indeterminate", "invalid": return true default: return false } }