优化审批流

This commit is contained in:
hero920103 committed 2026-10-07 22:56:35 +08:00
1 parent ffaadc26ae
commit 438145cae7
17 files changed
+1131 -187

No files matched your search

+90 -37
View File
@@ -10,6 +10,7 @@ import (
"server/models"
"server/services"
approvalEngine "server/services/approval"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
@@ -62,16 +63,17 @@ func (c *BackendApprovalController) FlowList() {
cnt, _ := models.Orm.QueryTable(new(models.TenantApprovalNode)).
Filter("flow_id", r.ID).Filter("is_deleted", 0).Count()
list = append(list, approvalFlowView{
ID: r.ID,
ModuleCode: r.ModuleCode,
BizType: r.BizType,
FlowCode: r.FlowCode,
FlowName: r.FlowName,
Description: r.Description,
IsDefault: r.IsDefault,
Status: r.Status,
NodeCount: int(cnt),
CreateTime: r.CreateTime.Format("2006-01-02 15:04:05"),
ID: r.ID,
ModuleCode: r.ModuleCode,
BizType: r.BizType,
FlowCode: r.FlowCode,
FlowName: r.FlowName,
Description: r.Description,
IsDefault: r.IsDefault,
Status: r.Status,
NodeCount: int(cnt),
CreateTime: r.CreateTime.Format("2006-01-02 15:04:05"),
ApplyDeptIDs: parseIDSlice(r.ApplyDeptIDs),
})
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok", "data": list}
@@ -105,17 +107,18 @@ func tenantFlowToView(f models.TenantApprovalFlow) approvalFlowView {
Filter("flow_id", f.ID).Filter("is_deleted", 0).
OrderBy("sort", "id").All(&nodes)
view := approvalFlowView{
ID: f.ID,
ModuleCode: f.ModuleCode,
BizType: f.BizType,
FlowCode: f.FlowCode,
FlowName: f.FlowName,
Description: f.Description,
IsDefault: f.IsDefault,
Status: f.Status,
NodeCount: len(nodes),
CreateTime: f.CreateTime.Format("2006-01-02 15:04:05"),
Nodes: make([]approvalNodePayload, 0, len(nodes)),
ID: f.ID,
ModuleCode: f.ModuleCode,
BizType: f.BizType,
FlowCode: f.FlowCode,
FlowName: f.FlowName,
Description: f.Description,
IsDefault: f.IsDefault,
Status: f.Status,
NodeCount: len(nodes),
CreateTime: f.CreateTime.Format("2006-01-02 15:04:05"),
ApplyDeptIDs: parseIDSlice(f.ApplyDeptIDs),
Nodes: make([]approvalNodePayload, 0, len(nodes)),
}
edgeNodes := make([]flowEdgeNode, 0, len(nodes))
for _, n := range nodes {
@@ -176,6 +179,18 @@ func (c *BackendApprovalController) FlowSave() {
g := buildGraph(p.Nodes, p.Edges)
now := time.Now()
// 适用部门写入前校验:需全部属于当前租户(空列表=全公司通用,直接放行)
if len(p.ApplyDeptIDs) > 0 {
count, _ := models.Orm.QueryTable(new(models.BackendOrganization)).
Filter("tid", tid).Filter("id__in", p.ApplyDeptIDs).Filter("delete_time__isnull", true).
Count()
if int(count) != len(p.ApplyDeptIDs) {
jsonErr(&c.Controller, 400, 400, "适用部门包含非本租户组织,请重新选择")
return
}
}
applyDeptCSV := joinIDs(p.ApplyDeptIDs)
o := models.Orm
var flowID uint64
var flowCode string
@@ -190,13 +205,14 @@ func (c *BackendApprovalController) FlowSave() {
flowCode = exist.FlowCode
_, err := o.QueryTable(new(models.TenantApprovalFlow)).
Filter("id", flowID).Filter("tid", tid).Update(orm.Params{
"module_code": p.ModuleCode,
"biz_type": p.BizType,
"flow_name": p.FlowName,
"description": p.Description,
"is_default": p.IsDefault,
"status": p.Status,
"update_time": now,
"module_code": p.ModuleCode,
"biz_type": p.BizType,
"flow_name": p.FlowName,
"description": p.Description,
"is_default": p.IsDefault,
"status": p.Status,
"apply_dept_ids": applyDeptCSV,
"update_time": now,
})
if err != nil {
jsonErr(&c.Controller, 500, 500, "保存失败: "+err.Error())
@@ -204,14 +220,15 @@ func (c *BackendApprovalController) FlowSave() {
}
} else {
row := models.TenantApprovalFlow{
Tid: tid,
ModuleCode: p.ModuleCode,
BizType: p.BizType,
FlowName: p.FlowName,
Description: p.Description,
IsDefault: p.IsDefault,
Status: p.Status,
CreateTime: now,
Tid: tid,
ModuleCode: p.ModuleCode,
BizType: p.BizType,
FlowName: p.FlowName,
Description: p.Description,
IsDefault: p.IsDefault,
Status: p.Status,
ApplyDeptIDs: applyDeptCSV,
CreateTime: now,
}
id, err := o.Insert(&row)
if err != nil {
@@ -305,6 +322,7 @@ func (c *BackendApprovalController) Pick() {
list := make([]map[string]interface{}, 0, len(rows))
for _, r := range rows {
list = append(list, map[string]interface{}{
"id": r.ID,
"module_code": r.ModuleCode,
"flow_code": r.FlowCode,
"flow_name": r.FlowName,
@@ -404,7 +422,10 @@ func (c *BackendApprovalController) Options() {
OrderBy("sort", "id").All(&og)
orgList := make([]map[string]interface{}, 0, len(og))
for _, o := range og {
orgList = append(orgList, map[string]interface{}{"id": o.ID, "name": o.OrgName})
orgList = append(orgList, map[string]interface{}{
"id": o.ID, "name": o.OrgName,
"parent_id": o.ParentID, "is_company": o.IsCompany,
})
}
var ps []models.BackendPosition
@@ -429,6 +450,38 @@ func (c *BackendApprovalController) Options() {
_ = c.ServeJSON()
}
// AvailableFlows GET /backend/approval/flow/available?biz_type=reimburse
// 业务侧“发起前拉可用流”:基于当前登录用户的部门,过滤启用的流程。
// 前端基于返回结果判定:空则提示配置;一条自动带入;多条下拉选。
func (c *BackendApprovalController) AvailableFlows() {
claims, err := requireBackend(&c.Controller)
if err != nil {
jsonErr(&c.Controller, 401, 401, err.Error())
return
}
tid := uint64(claims.TenantId)
bizType := strings.TrimSpace(c.GetString("biz_type"))
if bizType == "" {
jsonErr(&c.Controller, 400, 400, "缺少 biz_type 参数")
return
}
// 取当前登录用户的部门(未分配时 OrgID=0,仅命中“全公司通用”流)
var tu models.SystemTenantUser
var userOrgID uint64
if e := models.Orm.QueryTable(new(models.SystemTenantUser)).
Filter("tid", tid).Filter("uid", claims.UserID).Filter("delete_time__isnull", true).
One(&tu); e == nil {
userOrgID = tu.OrgID
}
list, err := approvalEngine.ListAvailableFlows(models.ApprovalScopeTenant, tid, bizType, userOrgID)
if err != nil {
jsonErr(&c.Controller, 500, 500, "查询失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok", "data": list}
_ = c.ServeJSON()
}
// ============================ 辅助函数 ============================
// firstNonEmptyStr 取字符串指针/普通字符串中第一个非空值
+53 -27
View File
@@ -10,6 +10,7 @@ import (
"server/models"
"server/services"
approvalEngine "server/services/approval"
"github.com/beego/beego/v2/client/orm"
beego "github.com/beego/beego/v2/server/web"
@@ -26,12 +27,12 @@ type PlatformApprovalController struct {
// ---- 配置侧 JSON 结构 ----
// approvalNodePayload 单个节点(前端画布节点)。
// node_type:1开始 2审批 3结束 4条件网关;pos_x/pos_y 为画布坐标回显;
// condition_expr 为该节点作为「条件网关出边目标」时的进入条件(由 edges 反推落库)。
// node_type:1开始 2审批 3结束 4判断 5通知;pos_x/pos_y 为画布坐标回显;
// condition_expr 为该节点作为「判断节点出边目标」时的进入条件(由 edges 反推落库)。
type approvalNodePayload struct {
NodeKey string `json:"node_key"`
NodeName string `json:"node_name"`
NodeType int8 `json:"node_type"` // 1开始 2审批 3结束 4条件网关
NodeType int8 `json:"node_type"` // 1开始 2审批 3结束 4判断 5通知
ApproverType int8 `json:"approver_type"` // 1指定用户 2指定角色 ...
ApproverIDs []uint64 `json:"approver_ids"`
MultiMode int8 `json:"multi_mode"` // 1或签 2会签
@@ -52,31 +53,33 @@ type approvalEdge struct {
// approvalFlowSave 保存流程(图:节点 + 连线,节点全量覆盖)
type approvalFlowSave struct {
ID uint64 `json:"id"`
ModuleCode string `json:"module_code"`
BizType string `json:"biz_type"`
FlowName string `json:"flow_name"`
Description string `json:"description"`
IsDefault int8 `json:"is_default"`
Status int8 `json:"status"`
Nodes []approvalNodePayload `json:"nodes"`
Edges []approvalEdge `json:"edges"`
ID uint64 `json:"id"`
ModuleCode string `json:"module_code"`
BizType string `json:"biz_type"`
FlowName string `json:"flow_name"`
Description string `json:"description"`
IsDefault int8 `json:"is_default"`
Status int8 `json:"status"`
ApplyDeptIDs []uint64 `json:"apply_dept_ids"` // 仅租户侧使用,空=全公司通用
Nodes []approvalNodePayload `json:"nodes"`
Edges []approvalEdge `json:"edges"`
}
// approvalFlowView 流程 + 节点 + 连线(详情返回)
type approvalFlowView struct {
ID uint64 `json:"id"`
ModuleCode string `json:"module_code"`
BizType string `json:"biz_type"`
FlowCode string `json:"flow_code"`
FlowName string `json:"flow_name"`
Description string `json:"description"`
IsDefault int8 `json:"is_default"`
Status int8 `json:"status"`
NodeCount int `json:"node_count"`
CreateTime string `json:"create_time"`
Nodes []approvalNodePayload `json:"nodes"`
Edges []approvalEdge `json:"edges"`
ID uint64 `json:"id"`
ModuleCode string `json:"module_code"`
BizType string `json:"biz_type"`
FlowCode string `json:"flow_code"`
FlowName string `json:"flow_name"`
Description string `json:"description"`
IsDefault int8 `json:"is_default"`
Status int8 `json:"status"`
NodeCount int `json:"node_count"`
CreateTime string `json:"create_time"`
ApplyDeptIDs []uint64 `json:"apply_dept_ids"` // 仅租户侧回显
Nodes []approvalNodePayload `json:"nodes"`
Edges []approvalEdge `json:"edges"`
}
// readApprovalBody 读取并解析 JSON 请求体
@@ -355,6 +358,7 @@ func (c *PlatformApprovalController) Pick() {
list := make([]map[string]interface{}, 0, len(rows))
for _, r := range rows {
list = append(list, map[string]interface{}{
"id": r.ID,
"module_code": r.ModuleCode,
"biz_type": r.BizType,
"flow_code": r.FlowCode,
@@ -457,6 +461,28 @@ func (c *PlatformApprovalController) BizTypes() {
_ = c.ServeJSON()
}
// AvailableFlows GET /platform/approval/flow/available?biz_type=domain
// 业务侧“发起前拉可用流”:返回当前 biz_type 启用的平台流程列表。
// 平台无部门概念,userOrgID 固定为 0(引擎内平台侧不做部门过滤)。
func (c *PlatformApprovalController) AvailableFlows() {
if _, err := requirePlatform(&c.Controller); err != nil {
jsonErr(&c.Controller, 401, 401, err.Error())
return
}
bizType := strings.TrimSpace(c.GetString("biz_type"))
if bizType == "" {
jsonErr(&c.Controller, 400, 400, "缺少 biz_type 参数")
return
}
list, err := approvalEngine.ListAvailableFlows(models.ApprovalScopePlatform, 0, bizType, 0)
if err != nil {
jsonErr(&c.Controller, 500, 500, "查询失败: "+err.Error())
return
}
c.Data["json"] = map[string]interface{}{"code": 200, "msg": "ok", "data": list}
_ = c.ServeJSON()
}
// ============================ 辅助函数 ============================
// approverDisplayName 优先姓名,回退账号(AdminUser.Account 为普通字符串)
@@ -467,7 +493,7 @@ func approverDisplayName(name *string, account string) string {
return strings.TrimSpace(account)
}
// validateApprovalNodes 校验审批节点:必须选定审批人来源且非空(开始/结束/条件网关跳过)
// validateApprovalNodes 校验审批节点:必须选定审批人来源且非空(开始/结束/判断/通知跳过)
func validateApprovalNodes(nodes []approvalNodePayload) error {
for i, nd := range nodes {
if nd.NodeType == models.ApprovalNodeStart || nd.NodeType == models.ApprovalNodeEnd ||
@@ -587,7 +613,7 @@ func rebuildEdges(nodes []flowEdgeNode) []approvalEdge {
return edges
}
// validateApprovalGraph 校验画布图的完整性:单一开始、审批节点≥1、网关出边≥2、全部可达、连线端点有效
// validateApprovalGraph 校验画布图的完整性:单一开始、审批节点≥1、判断节点出边≥2、全部可达、连线端点有效
func validateApprovalGraph(nodes []approvalNodePayload, edges []approvalEdge) error {
if len(nodes) == 0 {
return fmt.Errorf("流程至少需包含开始与审批节点")
@@ -632,7 +658,7 @@ func validateApprovalGraph(nodes []approvalNodePayload, edges []approvalEdge) er
}
for _, k := range condKeys {
if len(nextAdj[k]) < 2 {
return fmt.Errorf("条件网关需至少两条分支连线")
return fmt.Errorf("判断节点需至少两条分支连线")
}
}
visited := map[string]bool{}