327 lines
9.5 KiB
Go
327 lines
9.5 KiB
Go
package handler
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
"silk-server-go/internal/middleware"
|
|
"silk-server-go/internal/model"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// RegisterConsultationRoutes 注册专家会诊路由
|
|
func RegisterConsultationRoutes(rg *gin.RouterGroup, db *gorm.DB) {
|
|
read := middleware.RequirePermission(db, "consultation:read")
|
|
write := middleware.RequirePermission(db, "consultation:write")
|
|
rg.GET("/consultations", read, listConsultations(db))
|
|
rg.GET("/consultations/:id", read, getConsultation(db))
|
|
rg.POST("/consultations", write, createConsultation(db))
|
|
rg.PATCH("/consultations/:id", write, updateConsultation(db))
|
|
rg.POST("/consultations/:id/resolve", write, resolveConsultation(db))
|
|
rg.POST("/consultations/:id/archive", write, archiveConsultation(db))
|
|
}
|
|
|
|
// listConsultations 会诊单列表(status 过滤)
|
|
func listConsultations(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
q := applyRoomScope(db.Model(&model.Consultation{}), c, "room_id")
|
|
if status := c.Query("status"); status != "" {
|
|
q = q.Where("status = ?", status)
|
|
}
|
|
var list []model.Consultation
|
|
q.Order("created_at DESC").Find(&list)
|
|
fillConsultationRoomNames(db, list)
|
|
c.JSON(http.StatusOK, list)
|
|
}
|
|
}
|
|
|
|
func fillConsultationRoomNames(db *gorm.DB, list []model.Consultation) {
|
|
var rooms []model.Room
|
|
db.Select("id", "name").Find(&rooms)
|
|
names := make(map[string]string, len(rooms))
|
|
for _, r := range rooms {
|
|
names[r.ID] = r.Name
|
|
}
|
|
for i := range list {
|
|
if list[i].RoomID != nil {
|
|
if n, ok := names[*list[i].RoomID]; ok {
|
|
list[i].RoomName = &n
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// getConsultation 会诊单详情
|
|
func getConsultation(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
if !requireObjectAccess(c, db, "consultation", c.Param("id")) {
|
|
return
|
|
}
|
|
var t model.Consultation
|
|
if db.Where("id = ?", c.Param("id")).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"})
|
|
return
|
|
}
|
|
fillConsultationRoomNames(db, []model.Consultation{t})
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
// createConsultation 新建会诊单(可关联 LAMP 任务,自动打包病例快照)
|
|
func createConsultation(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
var body struct {
|
|
Title string `json:"title"`
|
|
Summary *string `json:"summary"`
|
|
RoomID *string `json:"roomId"`
|
|
BatchID *string `json:"batchId"`
|
|
LampTestID *string `json:"lampTestId"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
for _, id := range []*string{body.RoomID, body.BatchID, body.LampTestID} {
|
|
if id != nil && *id != "" && !isUUID(*id) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "关联 ID 不是合法的 UUID"})
|
|
return
|
|
}
|
|
}
|
|
if body.RoomID != nil && !canAccessRoom(db, c, body.RoomID) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "无权为该蚕房发起会诊"})
|
|
return
|
|
}
|
|
if body.BatchID != nil && !requireObjectAccess(c, db, "batch", *body.BatchID) {
|
|
return
|
|
}
|
|
if body.LampTestID != nil && !requireObjectAccess(c, db, "lamp_test", *body.LampTestID) {
|
|
return
|
|
}
|
|
|
|
rec := model.Consultation{
|
|
RoomID: body.RoomID,
|
|
BatchID: body.BatchID,
|
|
LampTestID: body.LampTestID,
|
|
Summary: body.Summary,
|
|
Status: "unassigned",
|
|
}
|
|
if body.Title != "" {
|
|
rec.Title = body.Title
|
|
} else {
|
|
rec.Title = "LAMP 交叉验证会诊"
|
|
if body.Summary != nil {
|
|
rec.Title = "专家会诊:" + *body.Summary
|
|
}
|
|
}
|
|
|
|
// 病例快照
|
|
var lamp *model.LampTest
|
|
if body.LampTestID != nil && *body.LampTestID != "" {
|
|
var lt model.LampTest
|
|
if db.Where("id = ?", *body.LampTestID).First(<).Error == nil {
|
|
lamp = <
|
|
if rec.RoomID == nil {
|
|
rec.RoomID = lt.RoomID
|
|
}
|
|
if rec.BatchID == nil {
|
|
rec.BatchID = lt.BatchID
|
|
}
|
|
}
|
|
}
|
|
snapshot := buildConsultationSnapshot(db, rec.RoomID, rec.BatchID, lamp)
|
|
raw, _ := json.Marshal(snapshot)
|
|
rec.Snapshot = raw
|
|
|
|
if err := db.Create(&rec).Error; err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "创建失败"})
|
|
return
|
|
}
|
|
c.JSON(http.StatusCreated, rec)
|
|
}
|
|
}
|
|
|
|
// buildConsultationSnapshot 打包病例快照(房间/巡检/LAMP/批次/天气)
|
|
func buildConsultationSnapshot(db *gorm.DB, roomID, batchID *string, lamp *model.LampTest) model.ConsultationSnapshot {
|
|
s := model.ConsultationSnapshot{CreatedAt: time.Now()}
|
|
if roomID != nil {
|
|
var room model.Room
|
|
if db.Where("id = ?", *roomID).First(&room).Error == nil {
|
|
s.RoomName = room.Name
|
|
}
|
|
var insp model.InspectionRecord
|
|
if db.Where("room_id = ?", *roomID).Order("created_at DESC").First(&insp).Error == nil {
|
|
s.Inspection = &insp
|
|
}
|
|
var alerts []model.WeatherAlert
|
|
db.Order("created_at DESC").Limit(3).Find(&alerts)
|
|
s.WeatherAlerts = alerts
|
|
}
|
|
if lamp != nil {
|
|
s.LampTest = lamp
|
|
}
|
|
if batchID != nil {
|
|
var b model.Batch
|
|
if db.Where("id = ?", *batchID).First(&b).Error == nil {
|
|
s.Batch = &b
|
|
}
|
|
} else if lamp != nil && lamp.BatchID != nil {
|
|
var b model.Batch
|
|
if db.Where("id = ?", *lamp.BatchID).First(&b).Error == nil {
|
|
s.Batch = &b
|
|
}
|
|
}
|
|
return s
|
|
}
|
|
|
|
// updateConsultation 更新会诊单(标题/摘要/状态流转)
|
|
func updateConsultation(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if !requireObjectAccess(c, db, "consultation", id) {
|
|
return
|
|
}
|
|
var t model.Consultation
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"})
|
|
return
|
|
}
|
|
updates, err := bindUpdates(c)
|
|
if err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if roomID, ok := updates["room_id"].(string); ok && !canAccessRoom(db, c, &roomID) {
|
|
c.JSON(http.StatusForbidden, gin.H{"error": "无权迁移会诊到指定蚕房"})
|
|
return
|
|
}
|
|
for _, ref := range []struct {
|
|
key string
|
|
kind string
|
|
}{
|
|
{key: "batch_id", kind: "batch"},
|
|
{key: "lamp_test_id", kind: "lamp_test"},
|
|
} {
|
|
if id, ok := updates[ref.key].(string); ok && !requireObjectAccess(c, db, ref.kind, id) {
|
|
return
|
|
}
|
|
}
|
|
if s, ok := updates["status"]; ok {
|
|
to, _ := s.(string)
|
|
if !model.ValidConsultationTransition(t.Status, to) {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "非法的会诊状态流转"})
|
|
return
|
|
}
|
|
now := time.Now()
|
|
switch to {
|
|
case "assigned":
|
|
updates["assigned_at"] = now
|
|
updates["sla_deadline"] = now.Add(24 * time.Hour)
|
|
case "accepted":
|
|
updates["accepted_at"] = now
|
|
case "needs_info":
|
|
updates["needs_info_at"] = now
|
|
}
|
|
}
|
|
if len(updates) > 0 {
|
|
db.Model(&model.Consultation{}).Where("id = ?", id).Updates(updates)
|
|
}
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
// resolveConsultation 专家出会诊意见与防控方案
|
|
func resolveConsultation(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if !requireObjectAccess(c, db, "consultation", id) {
|
|
return
|
|
}
|
|
var t model.Consultation
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"})
|
|
return
|
|
}
|
|
if !model.ValidConsultationTransition(t.Status, "resolved") {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "当前状态不能出方案"})
|
|
return
|
|
}
|
|
var body struct {
|
|
Opinion string `json:"opinion"`
|
|
Plan string `json:"plan"`
|
|
Reason string `json:"reason"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
|
return
|
|
}
|
|
if body.Plan == "" {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "防控方案不能为空"})
|
|
return
|
|
}
|
|
now := time.Now()
|
|
authorID := currentUserID(c)
|
|
updates := map[string]interface{}{
|
|
"status": "resolved",
|
|
"expert_id": authorID,
|
|
"opinion": body.Opinion,
|
|
"plan": body.Plan,
|
|
"resolved_at": now,
|
|
}
|
|
var versionCount int64
|
|
db.Model(&model.ConsultationOpinionVersion{}).Where("consultation_id = ?", id).Count(&versionCount)
|
|
version := int(versionCount) + 1
|
|
if err := db.Create(&model.ConsultationOpinionVersion{
|
|
ConsultationID: id,
|
|
Version: version,
|
|
AuthorID: authorID,
|
|
Opinion: body.Opinion,
|
|
Plan: body.Plan,
|
|
Reason: consultationStrPtr(body.Reason),
|
|
}).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存意见版本失败"})
|
|
return
|
|
}
|
|
if err := db.Model(&model.Consultation{}).Where("id = ?", id).Updates(updates).Error; err != nil {
|
|
c.JSON(http.StatusInternalServerError, gin.H{"error": "保存会诊方案失败"})
|
|
return
|
|
}
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|
|
|
|
func consultationStrPtr(value string) *string {
|
|
if value == "" {
|
|
return nil
|
|
}
|
|
return &value
|
|
}
|
|
|
|
// archiveConsultation 归档
|
|
func archiveConsultation(db *gorm.DB) gin.HandlerFunc {
|
|
return func(c *gin.Context) {
|
|
id := c.Param("id")
|
|
if !requireObjectAccess(c, db, "consultation", id) {
|
|
return
|
|
}
|
|
var t model.Consultation
|
|
if db.Where("id = ?", id).First(&t).Error != nil {
|
|
c.JSON(http.StatusNotFound, gin.H{"error": "consultation not found"})
|
|
return
|
|
}
|
|
if !model.ValidConsultationTransition(t.Status, "archived") {
|
|
c.JSON(http.StatusBadRequest, gin.H{"error": "仅已出方案的会诊可归档"})
|
|
return
|
|
}
|
|
now := time.Now()
|
|
db.Model(&model.Consultation{}).Where("id = ?", id).
|
|
Updates(map[string]interface{}{"status": "archived", "archived_at": now})
|
|
db.Where("id = ?", id).First(&t)
|
|
c.JSON(http.StatusOK, t)
|
|
}
|
|
}
|