优化审批流
This commit is contained in:
1 parent
ffaadc26ae
commit
438145cae7
17 files changed
+1131
-187
No files matched your search
@@ -568,6 +568,99 @@ func dedupeApprovers(in []Approver) []Approver {
|
||||
return out
|
||||
}
|
||||
|
||||
// ============================ 运行时:发起前的可用流查询 ============================
|
||||
|
||||
// AvailableFlow 业务侧选择用的轻量结构
|
||||
type AvailableFlow struct {
|
||||
FlowID uint64 `json:"flow_id"`
|
||||
FlowCode string `json:"flow_code"`
|
||||
FlowName string `json:"flow_name"`
|
||||
BizType string `json:"biz_type"`
|
||||
}
|
||||
|
||||
// ListAvailableFlows 按 bizType + 用户部门 过滤“当前可用”审批流。
|
||||
// 直接查询 flow 表中 biz_type 匹配且 status=1 未删除的流程。
|
||||
// 租户侧额外按 apply_dept_ids 部门过滤。
|
||||
// 平台侧(scope=2)忽略部门过滤(因平台 flow 无 apply_dept_ids)。
|
||||
func ListAvailableFlows(scope int8, tid uint64, bizType string, userOrgID uint64) ([]AvailableFlow, error) {
|
||||
// 租户侧预计算一次当前用户组织的“祖先链”(含自身),选中上级部门或公司均命中
|
||||
var path []uint64
|
||||
if scope == models.ApprovalScopeTenant && userOrgID > 0 {
|
||||
path = buildOrgPath(tid, userOrgID)
|
||||
}
|
||||
out := make([]AvailableFlow, 0)
|
||||
if scope == models.ApprovalScopePlatform {
|
||||
var rows []models.PlatformApprovalFlow
|
||||
_, _ = models.Orm.QueryTable(new(models.PlatformApprovalFlow)).
|
||||
Filter("biz_type", bizType).Filter("is_deleted", 0).Filter("status", 1).
|
||||
OrderBy("id").All(&rows)
|
||||
for _, f := range rows {
|
||||
out = append(out, AvailableFlow{FlowID: f.ID, FlowCode: f.FlowCode, FlowName: f.FlowName, BizType: f.BizType})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
var rows []models.TenantApprovalFlow
|
||||
_, _ = models.Orm.QueryTable(new(models.TenantApprovalFlow)).
|
||||
Filter("tid", tid).Filter("biz_type", bizType).Filter("is_deleted", 0).Filter("status", 1).
|
||||
OrderBy("id").All(&rows)
|
||||
for _, f := range rows {
|
||||
if !deptMatch(f.ApplyDeptIDs, path) {
|
||||
continue
|
||||
}
|
||||
out = append(out, AvailableFlow{FlowID: f.ID, FlowCode: f.FlowCode, FlowName: f.FlowName, BizType: f.BizType})
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// deptMatch csv 空视为全公司通用;否则与 path (包含自身与祖先部门ID) 任一命中即命中。
|
||||
func deptMatch(csv string, path []uint64) bool {
|
||||
csv = strings.TrimSpace(csv)
|
||||
if csv == "" {
|
||||
return true
|
||||
}
|
||||
if len(path) == 0 {
|
||||
return false
|
||||
}
|
||||
inPath := make(map[uint64]bool, len(path))
|
||||
for _, id := range path {
|
||||
inPath[id] = true
|
||||
}
|
||||
for _, part := range strings.Split(csv, ",") {
|
||||
if v, err := strconv.ParseUint(strings.TrimSpace(part), 10, 64); err == nil && inPath[v] {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// buildOrgPath 从当前部门沿 parent_id 向上追到根,返回含自身的 ID 链(环保护)
|
||||
func buildOrgPath(tid, orgID uint64) []uint64 {
|
||||
if orgID == 0 {
|
||||
return nil
|
||||
}
|
||||
var all []models.BackendOrganization
|
||||
_, _ = models.Orm.QueryTable(new(models.BackendOrganization)).
|
||||
Filter("tid", tid).Filter("delete_time__isnull", true).
|
||||
All(&all, "id", "parent_id")
|
||||
parent := make(map[uint64]uint64, len(all))
|
||||
for _, o := range all {
|
||||
parent[o.ID] = o.ParentID
|
||||
}
|
||||
out := []uint64{orgID}
|
||||
seen := map[uint64]bool{orgID: true}
|
||||
cur := orgID
|
||||
for {
|
||||
p, ok := parent[cur]
|
||||
if !ok || p == 0 || seen[p] {
|
||||
break
|
||||
}
|
||||
out = append(out, p)
|
||||
seen[p] = true
|
||||
cur = p
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// ============================ 运行时:提交 ============================
|
||||
|
||||
// Submit 提交单据进入审批,创建实例并按图展开首个可执行节点的任务。
|
||||
@@ -614,7 +707,8 @@ func Submit(in SubmitInput) (uint64, error) {
|
||||
|
||||
start := flow.ByKey[flow.StartKey]
|
||||
hasApproval, firstApprovalName, firstApprovalKey := anyApprovalNode(flow)
|
||||
node, approvers := nextExecutable(flow, in.FormData, start, in)
|
||||
notifyFn := func(n *Node) { fireNotifyTasks(uint64(instID), in, n) }
|
||||
node, approvers := nextExecutable(flow, in.FormData, start, in, notifyFn)
|
||||
if node == nil {
|
||||
// 无可执行审批节点
|
||||
if hasApproval {
|
||||
@@ -647,8 +741,9 @@ func anyApprovalNode(flow *Flow) (bool, string, string) {
|
||||
return false, "", ""
|
||||
}
|
||||
|
||||
// advanceFrom 从 cur 沿出边前进,遇条件网关求值选路,返回首个审批节点(nil 表示到终点)
|
||||
func advanceFrom(flow *Flow, formData map[string]interface{}, cur *Node) *Node {
|
||||
// advanceFrom 从 cur 沿出边前进,遇条件网关求值选路,返回首个审批节点(nil 表示到终点)。
|
||||
// onNotify 可选回调:当路径经过通知节点时触发,由调用方负责创建通知任务。
|
||||
func advanceFrom(flow *Flow, formData map[string]interface{}, cur *Node, onNotify ...func(*Node)) *Node {
|
||||
visited := make(map[string]bool)
|
||||
node := pickNext(flow, formData, cur)
|
||||
for node != nil {
|
||||
@@ -661,6 +756,11 @@ func advanceFrom(flow *Flow, formData map[string]interface{}, cur *Node) *Node {
|
||||
return node
|
||||
case models.ApprovalNodeEnd:
|
||||
return nil
|
||||
case models.ApprovalNodeNotify:
|
||||
if len(onNotify) > 0 && onNotify[0] != nil {
|
||||
onNotify[0](node)
|
||||
}
|
||||
node = pickNext(flow, formData, node)
|
||||
default: // start / condition:继续向后
|
||||
node = pickNext(flow, formData, node)
|
||||
}
|
||||
@@ -700,8 +800,9 @@ func pickNext(flow *Flow, formData map[string]interface{}, node *Node) *Node {
|
||||
}
|
||||
|
||||
// nextExecutable 沿图找到下一个「有有效审批人」的审批节点;无人审批节点自动跳过
|
||||
func nextExecutable(flow *Flow, formData map[string]interface{}, cur *Node, in SubmitInput) (*Node, []Approver) {
|
||||
node := advanceFrom(flow, formData, cur)
|
||||
// nextExecutable 获取下一个可执行审批节点及其审批人(跳过通知节点并通过 onNotify 回调触发)
|
||||
func nextExecutable(flow *Flow, formData map[string]interface{}, cur *Node, in SubmitInput, onNotify ...func(*Node)) (*Node, []Approver) {
|
||||
node := advanceFrom(flow, formData, cur, onNotify...)
|
||||
visited := make(map[string]bool)
|
||||
for node != nil {
|
||||
if visited[node.NodeKey] {
|
||||
@@ -715,7 +816,7 @@ func nextExecutable(flow *Flow, formData map[string]interface{}, cur *Node, in S
|
||||
return node, approvers
|
||||
}
|
||||
}
|
||||
node = advanceFrom(flow, formData, node)
|
||||
node = advanceFrom(flow, formData, node, onNotify...)
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
@@ -792,6 +893,34 @@ func createTasks(instID uint64, in SubmitInput, node Node, approvers []Approver)
|
||||
return err
|
||||
}
|
||||
|
||||
// fireNotifyTasks 为通知节点创建已读通知任务(不阻塞流程)
|
||||
func fireNotifyTasks(instID uint64, in SubmitInput, notifyNode *Node) {
|
||||
recipients, _ := resolveApprovers(in.Scope, *notifyNode, in)
|
||||
recipients = dedupeApprovers(recipients)
|
||||
if len(recipients) == 0 {
|
||||
return
|
||||
}
|
||||
now := time.Now()
|
||||
tasks := make([]models.ApprovalTask, 0, len(recipients))
|
||||
for _, r := range recipients {
|
||||
tasks = append(tasks, models.ApprovalTask{
|
||||
InstanceID: instID,
|
||||
Scope: in.Scope,
|
||||
Tid: in.Tid,
|
||||
BizType: in.BizType,
|
||||
BizID: in.BizID,
|
||||
NodeKey: notifyNode.NodeKey,
|
||||
NodeName: notifyNode.NodeName,
|
||||
Sort: notifyNode.Sort,
|
||||
ApproverID: r.ID,
|
||||
ApproverName: r.Name,
|
||||
Status: models.ApprovalTaskNotified,
|
||||
FinishTime: &now,
|
||||
})
|
||||
}
|
||||
_, _ = models.Orm.InsertMulti(len(tasks), tasks)
|
||||
}
|
||||
|
||||
// finishInstance 结束实例
|
||||
func finishInstance(instID uint64, status int8) {
|
||||
now := time.Now()
|
||||
@@ -947,7 +1076,8 @@ func advance(instID uint64, currentKey string, in ApproveInput) (*ApproveResult,
|
||||
formData := instanceFormData(&inst)
|
||||
si := submitFromInstance(&inst, formData)
|
||||
cur := flow.ByKey[currentKey]
|
||||
node, approvers := nextExecutable(flow, formData, cur, si)
|
||||
notifyFn := func(n *Node) { fireNotifyTasks(instID, si, n) }
|
||||
node, approvers := nextExecutable(flow, formData, cur, si, notifyFn)
|
||||
if node == nil {
|
||||
finishInstance(instID, models.ApprovalInstanceApproved)
|
||||
return &ApproveResult{
|
||||
|
||||
Reference in new issue
Block a user