feat(server-go): 专家会诊(病例快照/状态流转/意见与方案,#18)

This commit is contained in:
weijuesen
2026-08-12 19:04:08 +08:00
parent 3db6943c63
commit bf734c1a52
6 changed files with 370 additions and 0 deletions
+52
View File
@@ -0,0 +1,52 @@
package model
import (
"encoding/json"
"time"
)
// Consultation 专家会诊单
type Consultation 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"`
LampTestID *string `gorm:"column:lamp_test_id;type:uuid;index" json:"lampTestId,omitempty"`
Title string `gorm:"size:128" json:"title"`
Summary *string `gorm:"type:text" json:"summary,omitempty"`
Snapshot json.RawMessage `gorm:"type:jsonb" json:"snapshot,omitempty"`
Status string `gorm:"size:16;default:pending" json:"status"` // pending/consulting/resolved/archived
ExpertID *string `gorm:"column:expert_id;type:uuid" json:"expertId,omitempty"`
Opinion *string `gorm:"type:text" json:"opinion,omitempty"`
Plan *string `gorm:"type:text" json:"plan,omitempty"`
ResolvedAt *time.Time `gorm:"column:resolved_at;type:timestamptz" json:"resolvedAt,omitempty"`
ArchivedAt *time.Time `gorm:"column:archived_at;type:timestamptz" json:"archivedAt,omitempty"`
CreatedAt time.Time `gorm:"type:timestamptz" json:"createdAt"`
UpdatedAt time.Time `gorm:"type:timestamptz" json:"updatedAt"`
RoomName *string `gorm:"-" json:"roomName,omitempty"`
}
func (Consultation) TableName() string { return "consultations" }
// ValidConsultationTransition 状态流转规则:pending→consulting/resolvedconsulting→resolvedresolved→archived
func ValidConsultationTransition(from, to string) bool {
switch from {
case "pending":
return to == "consulting" || to == "resolved"
case "consulting":
return to == "resolved"
case "resolved":
return to == "archived"
default:
return false
}
}
// ConsultationSnapshot 病例快照(会诊时打包,避免引用数据变化)
type ConsultationSnapshot struct {
RoomName string `json:"roomName,omitempty"`
LampTest *LampTest `json:"lampTest,omitempty"`
Inspection *InspectionRecord `json:"inspection,omitempty"`
Batch *Batch `json:"batch,omitempty"`
WeatherAlerts []WeatherAlert `json:"weatherAlerts,omitempty"`
CreatedAt time.Time `json:"createdAt"`
}
@@ -0,0 +1,57 @@
package model
import (
"encoding/json"
"testing"
"time"
)
func TestValidConsultationTransition(t *testing.T) {
ok := [][2]string{
{"pending", "consulting"},
{"pending", "resolved"},
{"consulting", "resolved"},
{"resolved", "archived"},
}
for _, c := range ok {
if !ValidConsultationTransition(c[0], c[1]) {
t.Errorf("%s → %s 应合法", c[0], c[1])
}
}
bad := [][2]string{
{"pending", "archived"},
{"resolved", "resolved"},
{"archived", "pending"},
{"", "resolved"},
}
for _, c := range bad {
if ValidConsultationTransition(c[0], c[1]) {
t.Errorf("%s → %s 不应合法", c[0], c[1])
}
}
}
func TestConsultationSnapshotJSON(t *testing.T) {
now := time.Now()
s := ConsultationSnapshot{
RoomName: "蚕房1#",
LampTest: &LampTest{ID: "lamp-1", Status: "resulted"},
Inspection: &InspectionRecord{ID: "insp-1", AIStatus: "done"},
Batch: &Batch{ID: "batch-1", Name: "批次A"},
WeatherAlerts: []WeatherAlert{{Disease: "白僵病", Level: "orange"}},
CreatedAt: now,
}
raw, err := json.Marshal(s)
if err != nil {
t.Fatalf("快照序列化失败: %v", err)
}
var m map[string]any
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatalf("快照反序列化失败: %v", err)
}
for _, key := range []string{"roomName", "lampTest", "inspection", "batch", "weatherAlerts", "createdAt"} {
if _, ok := m[key]; !ok {
t.Errorf("快照缺少字段 %s", key)
}
}
}
@@ -39,6 +39,8 @@ var AllPermissions = []PermissionDef{
{"lamp:write", "LAMP 检测管理", "新建、编辑、录入 LAMP 检测结果"},
{"consumable:read", "耗材查看", "查看耗材库存与预警"},
{"consumable:write", "耗材管理", "新增、编辑、删除耗材"},
{"consultation:read", "会诊查看", "查看专家会诊单与病例快照"},
{"consultation:write", "会诊管理", "发起会诊、出具意见与防控方案"},
{"user:manage", "用户管理", "管理用户、角色和权限"},
{"audit:read", "审计查看", "查看审计日志"},
}
@@ -56,6 +58,7 @@ var RolePermissionMap = map[string][]string{
"weather:read",
"lamp:read", "lamp:write",
"consumable:read", "consumable:write",
"consultation:read", "consultation:write",
"user:manage", "audit:read",
},
RoleOperator: {
@@ -69,6 +72,7 @@ var RolePermissionMap = map[string][]string{
"weather:read",
"lamp:read", "lamp:write",
"consumable:read", "consumable:write",
"consultation:read", "consultation:write",
},
RoleViewer: {
"dashboard:view", "room:read", "device:read",
@@ -80,6 +84,7 @@ var RolePermissionMap = map[string][]string{
"weather:read",
"lamp:read",
"consumable:read",
"consultation:read",
},
RoleFarmer: {
"dashboard:view", "room:read", "device:read", "device:control",
@@ -91,5 +96,6 @@ var RolePermissionMap = map[string][]string{
"weather:read",
"lamp:read",
"consumable:read",
"consultation:read",
},
}