Files
2026-07-09 14:16:56 +08:00

346 lines
8.9 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package controller
import (
"encoding/json"
"net/http"
"strconv"
"time"
"ezops/model"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
)
type flowStep struct {
Name string `json:"name"`
ApproverRole string `json:"approver_role"`
StepType string `json:"step_type"` // audit, execute
}
// ListProcesses 获取审批流程列表 // Logan.Li
func (h *ApprovalHandler) ListProcesses(c *gin.Context) {
var query model.PageQuery
c.ShouldBindQuery(&query)
db := h.db.Model(&model.ApprovalProcess{}).
Preload("Template").
Preload("ApplicantUser").
Preload("Steps", func(db *gorm.DB) *gorm.DB {
return db.Order("step ASC")
})
if status := c.Query("status"); status != "" {
db = db.Where("status = ?", status)
}
if category := c.Query("category"); category != "" {
db = db.Joins("JOIN sys_approval_templates ON sys_approval_templates.id = approval_processes.template_id").
Where("sys_approval_templates.category = ?", category)
}
var total int64
db.Count(&total)
var processes []model.ApprovalProcess
db.Order("created_at DESC").Offset(query.GetOffset()).Limit(query.GetLimit()).Find(&processes)
// Preload approver user for each step
for i := range processes {
for j := range processes[i].Steps {
h.db.Preload("ApproverUser").First(&processes[i].Steps[j], processes[i].Steps[j].ID)
}
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: model.ListResponse{
List: processes,
Total: total,
},
})
}
// GetProcess 获取审批流程详情 // Logan.Li
func (h *ApprovalHandler) GetProcess(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var process model.ApprovalProcess
if err := h.db.Preload("Template").Preload("ApplicantUser").
Preload("Steps.ApproverUser").Preload("Steps", func(db *gorm.DB) *gorm.DB {
return db.Order("step ASC")
}).First(&process, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{
Code: 404,
Message: "流程不存在",
})
return
}
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "success",
Data: process,
})
}
// CreateProcess 创建审批流程 — 使用审批流程配置 (sys_approval_workflows) // Logan.Li
func (h *ApprovalHandler) CreateProcess(c *gin.Context) {
var req model.CreateApprovalProcessRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{
Code: 400,
Message: "请求参数错误: " + err.Error(),
})
return
}
userIDVal, _ := c.Get("userID")
userID := toUint(userIDVal)
var template model.ApprovalTemplate
if err := h.db.First(&template, req.TemplateID).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "模板不存在"})
return
}
// 查找该分类下的默认/活跃审批流程配置
var workflow model.ApprovalWorkflow
if err := h.db.Where("category = ? AND is_active = ?", template.Category, true).
Order("is_default DESC").First(&workflow).Error; err != nil {
// 无工作流配置,回退到模板 flow_config
}
// 获取工作流步骤(跳过 submit 类型)
var workflowSteps []model.ApprovalWorkflowStep
stepCount := 1
var approvalSteps []struct {
name string
stepType string
approverIDs []uint
}
if workflow.ID > 0 {
h.db.Where("workflow_id = ?", workflow.ID).Order("step_number ASC").Find(&workflowSteps)
for _, ws := range workflowSteps {
if ws.StepType == "submit" {
continue // 提交步骤由申请人完成,不生成审批节点
}
stepCount++
approvalSteps = append(approvalSteps, struct {
name string
stepType string
approverIDs []uint
}{ws.Name, ws.StepType, ParseApproverIDs(ws.ApproverIDs)})
}
}
totalSteps := len(approvalSteps)
if totalSteps == 0 {
totalSteps = 1
}
process := model.ApprovalProcess{
TemplateID: req.TemplateID,
Title: req.Title,
Applicant: userID,
FormData: req.FormData,
Status: "pending",
CurrentStep: 1,
TotalSteps: totalSteps,
}
tx := h.db.Begin()
if err := tx.Create(&process).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建失败: " + err.Error()})
return
}
// 创建审批步骤(仅 audit + execute)
for i, as := range approvalSteps {
stepType := as.stepType
if stepType == "" {
// 最后一步默认执行,其余审核
if i == len(approvalSteps)-1 {
stepType = "execute"
} else {
stepType = "audit"
}
}
approverID := uint(1) // 默认管理员
if len(as.approverIDs) > 0 {
approverID = as.approverIDs[0]
}
step := model.ApprovalStep{
ProcessID: process.ID,
Step: i + 1,
StepType: stepType,
Approver: approverID,
Status: "pending",
Comment: as.name, // 存步骤名称
}
if err := tx.Create(&step).Error; err != nil {
tx.Rollback()
c.JSON(http.StatusInternalServerError, model.Response{Code: 500, Message: "创建步骤失败"})
return
}
}
tx.Commit()
// 重新加载完整数据
h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, process.ID)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "申请已提交",
Data: process,
})
}
// ParseApproverIDs 解析审批人JSON数组
func ParseApproverIDs(raw string) []uint {
if raw == "" {
return nil
}
var ids []uint
if err := json.Unmarshal([]byte(raw), &ids); err != nil {
return nil
}
return ids
}
func toUint(v interface{}) uint {
switch val := v.(type) {
case float64:
return uint(val)
case uint:
return val
case int:
return uint(val)
default:
return 0
}
}
// ApproveProcess 审批通过 // Logan.Li
func (h *ApprovalHandler) ApproveProcess(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req model.ApproveRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请求参数错误"})
return
}
userIDVal, _ := c.Get("userID")
approverID := toUint(userIDVal)
var process model.ApprovalProcess
if err := h.db.First(&process, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
if process.Status != "pending" {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "该申请已处理"})
return
}
now := time.Now()
// 查找当前步骤
var currentStep model.ApprovalStep
if err := h.db.Where("process_id = ? AND step = ?", process.ID, process.CurrentStep).First(&currentStep).Error; err != nil {
// 无步骤记录(旧数据兼容):直接通过
h.db.Model(&process).Updates(map[string]interface{}{
"status": "approved",
"approved_at": &now,
})
h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, process.ID)
c.JSON(http.StatusOK, model.Response{Code: 200, Message: "审批通过", Data: process})
return
}
tx := h.db.Begin()
// 更新当前步骤为通过
tx.Model(&currentStep).Updates(map[string]interface{}{
"status": "approved",
"approver": approverID,
"comment": req.Comment,
"approved_at": &now,
})
// 推进到下一步 或 标记全部通过
if process.CurrentStep >= process.TotalSteps {
tx.Model(&process).Updates(map[string]interface{}{
"status": "approved",
"approved_at": &now,
})
} else {
tx.Model(&process).Update("current_step", process.CurrentStep+1)
}
tx.Commit()
h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, process.ID)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "审批通过",
Data: process,
})
}
// RejectProcess 审批拒绝 // Logan.Li
func (h *ApprovalHandler) RejectProcess(c *gin.Context) {
id, _ := strconv.Atoi(c.Param("id"))
var req model.ApproveRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "请求参数错误"})
return
}
userIDVal, _ := c.Get("userID")
approverID := toUint(userIDVal)
var process model.ApprovalProcess
if err := h.db.First(&process, id).Error; err != nil {
c.JSON(http.StatusNotFound, model.Response{Code: 404, Message: "流程不存在"})
return
}
if process.Status != "pending" {
c.JSON(http.StatusBadRequest, model.Response{Code: 400, Message: "该申请已处理"})
return
}
now := time.Now()
tx := h.db.Begin()
// 更新当前步骤为拒绝
tx.Model(&model.ApprovalStep{}).Where("process_id = ? AND step = ?", process.ID, process.CurrentStep).
Updates(map[string]interface{}{
"status": "rejected",
"approver": approverID,
"comment": req.Comment,
"approved_at": &now,
})
tx.Model(&process).Updates(map[string]interface{}{
"status": "rejected",
"rejected_at": &now,
"reject_reason": req.Comment,
})
tx.Commit()
h.db.Preload("Template").Preload("ApplicantUser").Preload("Steps.ApproverUser").First(&process, process.ID)
c.JSON(http.StatusOK, model.Response{
Code: 200,
Message: "已拒绝",
Data: process,
})
}