diff --git a/backend/internal/handler/ticket.go b/backend/internal/handler/ticket.go
index f017ceb..08c7f5b 100644
--- a/backend/internal/handler/ticket.go
+++ b/backend/internal/handler/ticket.go
@@ -217,6 +217,14 @@ func CreateTicket(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
+
+ // Auto-fill workflow_id from instance if not provided
+ if input.WorkflowID == nil || *input.WorkflowID == 0 {
+ var inst model.Instance
+ if model.DB.First(&inst, input.InstanceID).Error == nil && inst.WorkflowID != nil {
+ input.WorkflowID = inst.WorkflowID
+ }
+ }
status := "pending"
priority := "medium"
@@ -348,14 +356,20 @@ func CreateTicket(c *gin.Context) {
// POST /api/v1/tickets/:id/approve
func ApproveTicket(c *gin.Context) {
id := c.Param("id")
- userID, _, _ := middleware.GetCurrentUser(c)
-
+ userID, userRole, _ := middleware.GetCurrentUser(c)
+
+ // Role check: only admin/dba/auditor can approve
+ if userRole != "admin" && userRole != "dba" && userRole != "auditor" {
+ c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:审核操作需要管理员、DBA 或审核员角色"})
+ return
+ }
+
var ticket model.Ticket
if err := model.DB.First(&ticket, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"})
return
}
-
+
var input struct {
Action string `json:"action" binding:"required"` // approve, reject
Comment *string `json:"comment"`
@@ -364,31 +378,25 @@ func ApproveTicket(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
return
}
-
+
now := time.Now()
-
+
// Update approval record — auto-create if missing (legacy tickets)
var approval model.TicketApproval
if err := model.DB.Where("ticket_id = ? AND approver_id = ? AND action IS NULL", id, userID).First(&approval).Error; err != nil {
- // No approval record — check if user is authorized to review
- _, userRole, _ := middleware.GetCurrentUser(c)
- if userRole == "admin" || userRole == "dba" {
- stepName := "审核"
- stepType := "audit"
- approverRole := userRole
- approval = model.TicketApproval{
- TicketID: ticket.ID,
- StepNumber: 1,
- StepName: &stepName,
- StepType: &stepType,
- ApproverID: &userID,
- ApproverRole: &approverRole,
- }
- model.DB.Create(&approval)
- } else {
- c.JSON(http.StatusBadRequest, gin.H{"detail": "No pending approval for you"})
- return
+ // No approval record — authorized role can create one
+ stepName := "审核"
+ stepType := "audit"
+ approverRole := userRole
+ approval = model.TicketApproval{
+ TicketID: ticket.ID,
+ StepNumber: 1,
+ StepName: &stepName,
+ StepType: &stepType,
+ ApproverID: &userID,
+ ApproverRole: &approverRole,
}
+ model.DB.Create(&approval)
}
approval.Action = &input.Action
@@ -420,7 +428,14 @@ func ApproveTicket(c *gin.Context) {
// POST /api/v1/tickets/:id/execute
func ExecuteTicket(c *gin.Context) {
id := c.Param("id")
-
+
+ // Role check: only admin/dba can execute
+ _, userRole, _ := middleware.GetCurrentUser(c)
+ if userRole != "admin" && userRole != "dba" {
+ c.JSON(http.StatusForbidden, gin.H{"detail": "权限不足:执行操作需要管理员或 DBA 角色"})
+ return
+ }
+
var ticket model.Ticket
if err := model.DB.First(&ticket, id).Error; err != nil {
c.JSON(http.StatusNotFound, gin.H{"detail": "Ticket not found"})
diff --git a/frontend/src/pages/UsersPage.tsx b/frontend/src/pages/UsersPage.tsx
index d7e16f7..5087b78 100644
--- a/frontend/src/pages/UsersPage.tsx
+++ b/frontend/src/pages/UsersPage.tsx
@@ -487,9 +487,10 @@ export default function UsersPage() {