优化审批流
This commit is contained in:
1 parent
ffaadc26ae
commit
438145cae7
17 files changed
+1131
-187
No files matched your search
@@ -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 取字符串指针/普通字符串中第一个非空值
|
||||
|
||||
@@ -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{}
|
||||
|
||||
Reference in new issue
Block a user