feat: 完善环境规则、会诊治理、知识审核与效果评估
This commit is contained in:
@@ -71,10 +71,10 @@ func getConsultation(db *gorm.DB) gin.HandlerFunc {
|
||||
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"`
|
||||
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 {
|
||||
@@ -93,7 +93,7 @@ func createConsultation(db *gorm.DB) gin.HandlerFunc {
|
||||
BatchID: body.BatchID,
|
||||
LampTestID: body.LampTestID,
|
||||
Summary: body.Summary,
|
||||
Status: "pending",
|
||||
Status: "unassigned",
|
||||
}
|
||||
if body.Title != "" {
|
||||
rec.Title = body.Title
|
||||
@@ -183,6 +183,16 @@ func updateConsultation(db *gorm.DB) gin.HandlerFunc {
|
||||
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)
|
||||
@@ -208,6 +218,7 @@ func resolveConsultation(db *gorm.DB) gin.HandlerFunc {
|
||||
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()})
|
||||
@@ -218,19 +229,44 @@ func resolveConsultation(db *gorm.DB) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
authorID := currentUserID(c)
|
||||
updates := map[string]interface{}{
|
||||
"status": "resolved",
|
||||
"expert_id": currentUserID(c),
|
||||
"expert_id": authorID,
|
||||
"opinion": body.Opinion,
|
||||
"plan": body.Plan,
|
||||
"resolved_at": now,
|
||||
}
|
||||
db.Model(&model.Consultation{}).Where("id = ?", id).Updates(updates)
|
||||
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) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
package handler
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
func TestConsultationStrPtr(t *testing.T) {
|
||||
if consultationStrPtr("") != nil {
|
||||
t.Error("空字符串应为 nil")
|
||||
}
|
||||
if got := consultationStrPtr("原因"); got == nil || *got != "原因" {
|
||||
t.Errorf("非空字符串应返回指针: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func TestCanReviewKnowledge(t *testing.T) {
|
||||
gin.SetMode(gin.TestMode)
|
||||
rec := httptest.NewRecorder()
|
||||
c, _ := gin.CreateTestContext(rec)
|
||||
c.Request = httptest.NewRequest(http.MethodGet, "/", nil)
|
||||
if canReviewKnowledge(c) {
|
||||
t.Error("nil context 不应通过")
|
||||
}
|
||||
}
|
||||
@@ -24,6 +24,9 @@ func RegisterHealthProfileRoutes(rg *gin.RouterGroup, db *gorm.DB) {
|
||||
rg.GET("/health-profiles/:roomId",
|
||||
middleware.RequirePermission(db, "room:read"),
|
||||
roomHealthProfile(db))
|
||||
rg.GET("/health-profiles/:roomId/effect",
|
||||
middleware.RequirePermission(db, "room:read"),
|
||||
roomEffectReport(db))
|
||||
}
|
||||
|
||||
// roomHealthProfile 单蚕房健康画像:巡检风险/检测结果/事件聚合 → 综合健康分
|
||||
@@ -99,3 +102,54 @@ func roomHealthProfile(db *gorm.DB) gin.HandlerFunc {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// roomEffectReport 处置前后窗口效果报告。
|
||||
func roomEffectReport(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
roomID := c.Param("roomId")
|
||||
now := time.Now()
|
||||
parse := func(key string, fallback time.Time) time.Time {
|
||||
raw := c.Query(key)
|
||||
if raw == "" {
|
||||
return fallback
|
||||
}
|
||||
t, err := time.Parse(time.RFC3339, raw)
|
||||
if err != nil {
|
||||
return fallback
|
||||
}
|
||||
return t
|
||||
}
|
||||
window := service.EffectWindow{
|
||||
BeforeStart: parse("beforeStart", now.Add(-60*24*time.Hour)),
|
||||
BeforeEnd: parse("beforeEnd", now.Add(-30*24*time.Hour)),
|
||||
AfterStart: parse("afterStart", now.Add(-30*24*time.Hour)),
|
||||
AfterEnd: parse("afterEnd", now),
|
||||
}
|
||||
before, after := service.EffectMetrics{}, service.EffectMetrics{}
|
||||
loadEffectMetrics := func(start, end time.Time) service.EffectMetrics {
|
||||
metrics := service.EffectMetrics{}
|
||||
db.Table("inspection_records").
|
||||
Where("room_id = ? AND ai_status = 'done' AND COALESCE(is_mock, false) = false AND risk_score IS NOT NULL AND created_at >= ? AND created_at < ?", roomID, start, end).
|
||||
Pluck("risk_score", &metrics.RiskSamples)
|
||||
var lampTotal int64
|
||||
db.Model(&model.LampTest{}).
|
||||
Where("room_id = ? AND status = 'resulted' AND created_at >= ? AND created_at < ?", roomID, start, end).
|
||||
Count(&lampTotal)
|
||||
metrics.LampTotal = int(lampTotal)
|
||||
var lampPositive int64
|
||||
db.Model(&model.LampTest{}).
|
||||
Where("room_id = ? AND status = 'resulted' AND result = 'positive' AND created_at >= ? AND created_at < ?", roomID, start, end).
|
||||
Count(&lampPositive)
|
||||
metrics.LampPositive = int(lampPositive)
|
||||
return metrics
|
||||
}
|
||||
before = loadEffectMetrics(window.BeforeStart, window.BeforeEnd)
|
||||
after = loadEffectMetrics(window.AfterStart, window.AfterEnd)
|
||||
var recurrences int64
|
||||
db.Model(&model.DiseaseEvent{}).
|
||||
Where("room_id = ? AND status = 'reopened' AND created_at >= ? AND created_at < ?", roomID, window.AfterStart, window.AfterEnd).
|
||||
Count(&recurrences)
|
||||
after.Recurrences = int(recurrences)
|
||||
c.JSON(http.StatusOK, service.EvaluateControlEffect(roomID, window, before, after))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,15 @@ func listDiseases(db *gorm.DB) gin.HandlerFunc {
|
||||
if enabled := c.Query("enabled"); enabled != "" {
|
||||
q = q.Where("enabled = ?", enabled == "true")
|
||||
}
|
||||
if status := c.Query("status"); status != "" {
|
||||
if !canReviewKnowledge(c) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权限查看未发布内容"})
|
||||
return
|
||||
}
|
||||
q = q.Where("status = ?", status)
|
||||
} else {
|
||||
q = q.Where("status = 'published'")
|
||||
}
|
||||
var list []model.Disease
|
||||
q.Order("sort_order ASC, name ASC").Find(&list)
|
||||
c.JSON(http.StatusOK, list)
|
||||
@@ -94,6 +103,10 @@ func getDisease(db *gorm.DB) gin.HandlerFunc {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "disease not found"})
|
||||
return
|
||||
}
|
||||
if d.Status != "published" && !canReviewKnowledge(c) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "disease not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, d)
|
||||
}
|
||||
}
|
||||
@@ -107,6 +120,7 @@ func createDisease(db *gorm.DB) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
d.ID = ""
|
||||
d.Status = "draft"
|
||||
if d.Name == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "病种名称不能为空"})
|
||||
return
|
||||
@@ -134,6 +148,10 @@ func updateDisease(db *gorm.DB) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if status, ok := updates["status"].(string); ok && !model.ValidKnowledgeTransition(d.Status, status) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "非法的知识状态流转"})
|
||||
return
|
||||
}
|
||||
db.Model(&model.Disease{}).Where("id = ?", id).Updates(updates)
|
||||
}
|
||||
db.Where("id = ?", id).First(&d)
|
||||
@@ -165,6 +183,15 @@ func listArticles(db *gorm.DB) gin.HandlerFunc {
|
||||
if enabled := c.Query("enabled"); enabled != "" {
|
||||
q = q.Where("enabled = ?", enabled == "true")
|
||||
}
|
||||
if status := c.Query("status"); status != "" {
|
||||
if !canReviewKnowledge(c) {
|
||||
c.JSON(http.StatusForbidden, gin.H{"error": "无权限查看未发布内容"})
|
||||
return
|
||||
}
|
||||
q = q.Where("status = ?", status)
|
||||
} else {
|
||||
q = q.Where("status = 'published'")
|
||||
}
|
||||
var list []model.KnowledgeArticle
|
||||
q.Order("sort_order ASC, created_at DESC").Find(&list)
|
||||
c.JSON(http.StatusOK, list)
|
||||
@@ -179,6 +206,10 @@ func getArticle(db *gorm.DB) gin.HandlerFunc {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "article not found"})
|
||||
return
|
||||
}
|
||||
if a.Status != "published" && !canReviewKnowledge(c) {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "article not found"})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, a)
|
||||
}
|
||||
}
|
||||
@@ -192,6 +223,7 @@ func createArticle(db *gorm.DB) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
a.ID = ""
|
||||
a.Status = "draft"
|
||||
if a.Kind == "" || a.Title == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "文章类型和标题不能为空"})
|
||||
return
|
||||
@@ -219,6 +251,10 @@ func updateArticle(db *gorm.DB) gin.HandlerFunc {
|
||||
return
|
||||
}
|
||||
if len(updates) > 0 {
|
||||
if status, ok := updates["status"].(string); ok && !model.ValidKnowledgeTransition(a.Status, status) {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "非法的知识状态流转"})
|
||||
return
|
||||
}
|
||||
db.Model(&model.KnowledgeArticle{}).Where("id = ?", id).Updates(updates)
|
||||
}
|
||||
db.Where("id = ?", id).First(&a)
|
||||
@@ -226,6 +262,19 @@ func updateArticle(db *gorm.DB) gin.HandlerFunc {
|
||||
}
|
||||
}
|
||||
|
||||
func canReviewKnowledge(c *gin.Context) bool {
|
||||
user, ok := c.Get("user")
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
m, ok := user.(map[string]interface{})
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
role, _ := m["role"].(string)
|
||||
return role == "admin" || role == "operator"
|
||||
}
|
||||
|
||||
// deleteArticle 删除知识文章
|
||||
func deleteArticle(db *gorm.DB) gin.HandlerFunc {
|
||||
return func(c *gin.Context) {
|
||||
|
||||
Reference in New Issue
Block a user