217 lines
11 KiB
Go
217 lines
11 KiB
Go
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
|
|
}
|
|
}
|