Files
iqudo-top1/internal/sop/review.go
2026-08-08 22:58:35 +08:00

91 lines
3.6 KiB
Go

package sop
import (
"net/http"
"time"
"git.iwork-ai.com/xdc/iqudo-top1/internal/access"
"git.iwork-ai.com/xdc/iqudo-top1/internal/audit"
"git.iwork-ai.com/xdc/iqudo-top1/internal/auth"
"git.iwork-ai.com/xdc/iqudo-top1/internal/model"
"git.iwork-ai.com/xdc/iqudo-top1/internal/response"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type ReviewItem struct {
SOPID uint64 `json:"sop_id"`
SOPName string `json:"sop_name"`
Description string `json:"description"`
ScenarioID uint64 `json:"scenario_id"`
ScenarioName string `json:"scenario_name"`
VersionID uint64 `json:"version_id"`
Version int `json:"version"`
CreatorID uint64 `json:"creator_id"`
CreatorName string `json:"creator_name"`
SubmittedAt time.Time `json:"submitted_at"`
}
func (h *Handler) Reviews(c *gin.Context) {
principal, _ := auth.PrincipalFromContext(c)
items := make([]ReviewItem, 0)
query := h.db.Table("sop_versions sv").Select(
"s.id AS sop_id, s.name AS sop_name, s.description, sc.id AS scenario_id, sc.name AS scenario_name, " +
"sv.id AS version_id, sv.version, sv.created_by AS creator_id, u.display_name AS creator_name, sv.updated_at AS submitted_at",
).Joins("JOIN sops s ON s.id = sv.sop_id").Joins("JOIN scenarios sc ON sc.id = s.scenario_id").Joins("JOIN users u ON u.id = sv.created_by")
query = access.ScopeScenarios(query, principal, "sc")
if err := query.Where("sv.tenant_id = ? AND sv.status = ?", principal.TenantID, "reviewing").Order("sv.updated_at ASC").Scan(&items).Error; err != nil {
response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询审核任务失败")
return
}
response.OK(c, gin.H{"items": items, "total": len(items)})
}
func (h *Handler) Reject(c *gin.Context) {
principal, _ := auth.PrincipalFromContext(c)
sopID, ok := parseID(c, "id")
if !ok {
return
}
if !access.CanViewSOP(h.db, principal, sopID) {
response.Error(c, http.StatusNotFound, "NOT_FOUND", "SOP 不存在")
return
}
var input struct {
Reason string `json:"reason" binding:"required,max=1000"`
}
if err := c.ShouldBindJSON(&input); err != nil {
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "请填写退回原因")
return
}
var version model.SOPVersion
if err := h.db.Where("sop_id = ? AND tenant_id = ? AND status = ?", sopID, principal.TenantID, "reviewing").Order("version DESC").First(&version).Error; err != nil {
response.Error(c, http.StatusConflict, "NO_REVIEW_VERSION", "没有待审核版本")
return
}
if !auth.HasPermission(principal, "*") && version.CreatedBy == principal.UserID {
response.Error(c, http.StatusForbidden, "SELF_REVIEW_FORBIDDEN", "不能审核自己创建的 SOP")
return
}
err := h.db.Transaction(func(tx *gorm.DB) error {
if err := tx.Model(&version).Updates(map[string]interface{}{"status": "draft", "reviewed_by": nil}).Error; err != nil {
return err
}
var published int64
if err := tx.Model(&model.SOPVersion{}).Where("sop_id = ? AND tenant_id = ? AND status = ?", sopID, principal.TenantID, "published").Count(&published).Error; err != nil {
return err
}
status := "draft"
if published > 0 {
status = "published"
}
return tx.Model(&model.SOP{}).Where("id = ? AND tenant_id = ?", sopID, principal.TenantID).Update("status", status).Error
})
if err != nil {
response.Error(c, http.StatusInternalServerError, "REJECT_FAILED", "退回审核失败")
return
}
_ = audit.Record(h.db, principal, "reject", "sop", sopID, gin.H{"version": version.Version, "reason": input.Reason})
response.OK(c, gin.H{"id": sopID, "version": version.Version, "status": "draft"})
}