+ Next()
This commit is contained in:
parent
ffd2ea4320
commit
72b1108ec5
5 changed files with 232 additions and 32 deletions
|
|
@ -15,5 +15,5 @@
|
|||
"orders": [":RAND()"]
|
||||
}
|
||||
],
|
||||
"output": "{{$res.负责人.id}}"
|
||||
"output": "{{$in.0}}"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -53,15 +53,15 @@
|
|||
"props": { "src": "/workflows/assign/approve.html" }
|
||||
},
|
||||
"actions": ["驳回并重填", "驳回并关闭", "同意"],
|
||||
"user": { "process": "flows.select.manager", "args": ["id"] }
|
||||
"user": { "process": "flows.select.manager", "args": [2] }
|
||||
},
|
||||
{
|
||||
"name": "审批通过",
|
||||
"when": [{ "审批通过": "{{$data.审批状态}}", "=": "通过" }],
|
||||
"when": [{ "审批通过": "{{$out.审批结果}}", "=": "通过" }],
|
||||
"body": {
|
||||
"type": "markdown",
|
||||
"props": {
|
||||
"content": "审批通过, {{$data.项目名称}}商务负责人为: **{{$data.商务负责人名称}}**"
|
||||
"content": "审批通过, {{$out.项目名称}}商务负责人为: **{{$out.商务负责人名称}}**"
|
||||
}
|
||||
},
|
||||
"actions": ["关闭"],
|
||||
|
|
@ -69,11 +69,11 @@
|
|||
},
|
||||
{
|
||||
"name": "审批驳回",
|
||||
"when": [{ "审批通过": "{{$data.审批状态}}", "=": "驳回" }],
|
||||
"when": [{ "审批通过": "{{$out.审批结果}}", "=": "驳回" }],
|
||||
"body": {
|
||||
"type": "markdown",
|
||||
"props": {
|
||||
"content": "您的请求被驳回, 驳回原因: **{{$data.驳回原因}}**"
|
||||
"content": "您的请求被驳回, 驳回原因: **{{$out.驳回原因}}**"
|
||||
}
|
||||
},
|
||||
"actions": ["关闭"],
|
||||
|
|
|
|||
|
|
@ -1,6 +1,9 @@
|
|||
package workflow
|
||||
|
||||
import "github.com/yaoapp/xiang/share"
|
||||
import (
|
||||
"github.com/yaoapp/xiang/helper"
|
||||
"github.com/yaoapp/xiang/share"
|
||||
)
|
||||
|
||||
// WorkFlow 工作流配置结构
|
||||
type WorkFlow struct {
|
||||
|
|
@ -16,10 +19,11 @@ type WorkFlow struct {
|
|||
|
||||
// Node 工作流节点
|
||||
type Node struct {
|
||||
Name string `json:"name"`
|
||||
Body share.Render `json:"body,omitempty"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
User User `json:"user,omitempty"`
|
||||
Name string `json:"name"`
|
||||
Body share.Render `json:"body,omitempty"`
|
||||
Conditions []helper.Condition `json:"when,omitempty"`
|
||||
Actions []string `json:"actions,omitempty"`
|
||||
User User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// User 工作流相关用户读取条件
|
||||
|
|
|
|||
|
|
@ -6,7 +6,10 @@ import (
|
|||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou"
|
||||
gshare "github.com/yaoapp/gou/query/share"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/xiang/config"
|
||||
"github.com/yaoapp/xiang/helper"
|
||||
"github.com/yaoapp/xiang/share"
|
||||
|
|
@ -76,13 +79,15 @@ func (workflow *WorkFlow) Reload() *WorkFlow {
|
|||
}
|
||||
|
||||
// Process
|
||||
// 读取工作流 xiang.workflow.Get(uid, name, data_id)
|
||||
// 读取工作流 xiang.workflow.Open(uid, name, data_id)
|
||||
// 读取工作流 xiang.workflow.Find(id)
|
||||
// 保存工作流 xiang.workflow.Save(uid, name, node, input)
|
||||
// 进入下一个节点 xiang.workflow.Next(uid, id, input)
|
||||
// 跳转到指定节点 xiang.workflow.Goto(uid, id, node, input)
|
||||
|
||||
// API:
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/get
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/open
|
||||
// 读取工作流 GET /api/xiang/workflow/<工作流名称>/find/:id
|
||||
// 读取工作流配置 GET /api/xiang/workflow/<工作流名称>/setting
|
||||
// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由>
|
||||
|
||||
|
|
@ -92,12 +97,28 @@ func (workflow *WorkFlow) Setting(id int) {}
|
|||
// SetupAPIs 注册API
|
||||
func (workflow *WorkFlow) SetupAPIs(id int) {}
|
||||
|
||||
// Get 读取当前工作流(未完成的)
|
||||
func (workflow *WorkFlow) Get(uid int, id interface{}) map[string]interface{} {
|
||||
// Find 读取给定ID的工作流
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Find(id int) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
res := wflow.MustFind(id, gou.QueryParam{
|
||||
Select: []interface{}{
|
||||
"data_id", "id", "input", "output", "name",
|
||||
"user_id", "users",
|
||||
"node_name", "node_status",
|
||||
"status",
|
||||
"updated_at", "created_at",
|
||||
}})
|
||||
return res
|
||||
}
|
||||
|
||||
// Open 读取当前工作流(未完成的)
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Open(uid int, id interface{}) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{
|
||||
"data_id", "id", "input", "name",
|
||||
"data_id", "id", "input", "output", "name",
|
||||
"user_id", "users",
|
||||
"node_name", "node_status",
|
||||
"status",
|
||||
|
|
@ -109,6 +130,7 @@ func (workflow *WorkFlow) Get(uid int, id interface{}) map[string]interface{} {
|
|||
{Column: "user_ids", OP: "like", Value: fmt.Sprintf("%%|%d|%%", uid)},
|
||||
{Column: "status", Value: "进行中"},
|
||||
},
|
||||
Limit: 1,
|
||||
}
|
||||
rows := wflow.MustGet(params)
|
||||
if len(rows) > 0 {
|
||||
|
|
@ -123,14 +145,16 @@ func (workflow *WorkFlow) Get(uid int, id interface{}) map[string]interface{} {
|
|||
"status": "进行中",
|
||||
"node_status": "进行中",
|
||||
"input": map[string]interface{}{},
|
||||
"output": map[string]interface{}{},
|
||||
}
|
||||
}
|
||||
|
||||
// Save 保存工作流节点数据 此版本使用Like实现
|
||||
func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input) map[string]interface{} {
|
||||
// uid 当前处理人ID, id 数据ID
|
||||
func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input, outputs ...map[string]interface{}) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{"id", "input", "users"},
|
||||
Select: []interface{}{"id", "input", "output", "users"},
|
||||
Wheres: []gou.QueryWhere{
|
||||
{Column: "name", Value: workflow.Name},
|
||||
{Column: "data_id", Value: id},
|
||||
|
|
@ -148,9 +172,15 @@ func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input
|
|||
"user_id": uid,
|
||||
}
|
||||
users := []interface{}{uid}
|
||||
output := map[string]interface{}{}
|
||||
nodeInput := map[string]interface{}{}
|
||||
nodeInput[name] = input
|
||||
if len(outputs) > 0 {
|
||||
output = outputs[0]
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
nodeInput := map[string]interface{}{}
|
||||
if history, ok := rows[0].Get("input").(map[string]interface{}); ok {
|
||||
history[name] = input
|
||||
nodeInput = history
|
||||
}
|
||||
if last, ok := rows[0].Get("users").([]interface{}); ok {
|
||||
|
|
@ -158,30 +188,138 @@ func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input
|
|||
users = append(users, uid)
|
||||
users = helper.ArrayUnique(users)
|
||||
}
|
||||
nodeInput[name] = input
|
||||
if out, ok := rows[0].Get("output").(map[string]interface{}); ok {
|
||||
for k, v := range output {
|
||||
out[k] = v
|
||||
}
|
||||
output = out
|
||||
}
|
||||
data["id"] = rows[0].Get("id")
|
||||
data["input"] = nodeInput
|
||||
data["users"] = users
|
||||
} else {
|
||||
nodeInput := map[string]interface{}{}
|
||||
nodeInput[name] = input
|
||||
data["status"] = "进行中"
|
||||
data["node_status"] = "进行中"
|
||||
data["input"] = nodeInput
|
||||
data["users"] = users
|
||||
}
|
||||
|
||||
userIDs := []string{}
|
||||
for _, u := range users {
|
||||
userIDs = append(userIDs, fmt.Sprintf("|%d|", u))
|
||||
}
|
||||
data["users"] = users
|
||||
data["user_ids"] = strings.Join(userIDs, ",")
|
||||
data["input"] = nodeInput
|
||||
data["output"] = output
|
||||
id = wflow.MustSave(data)
|
||||
return wflow.MustFind(id, gou.QueryParam{})
|
||||
}
|
||||
|
||||
// Status 标记工作流状态
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Status(uid int, id int, output map[string]interface{}) {
|
||||
}
|
||||
|
||||
// Next 下一个工作流
|
||||
func (workflow *WorkFlow) Next(uid int, id int, input map[string]interface{}) {}
|
||||
// uid 当前处理人ID, id 工作流ID
|
||||
func (workflow *WorkFlow) Next(uid int, id int, output map[string]interface{}) map[string]interface{} {
|
||||
wflow := workflow.Find(id)
|
||||
currNode, ok := wflow["node_name"].(string)
|
||||
if !ok {
|
||||
exception.New("流程数据异常: 当前节点信息错误", 500).Ctx(currNode).Throw()
|
||||
}
|
||||
|
||||
// 合并数据输出
|
||||
if out, ok := wflow["output"].(map[string]interface{}); ok {
|
||||
for key, value := range output {
|
||||
out[key] = value
|
||||
}
|
||||
output = out
|
||||
}
|
||||
|
||||
// 读取下一个节点
|
||||
data := map[string]interface{}{
|
||||
"$in": wflow["input"],
|
||||
"$input": wflow["input"],
|
||||
"$out": output,
|
||||
"$outupt": output,
|
||||
}
|
||||
nextNode := workflow.nextNode(currNode, data)
|
||||
nextUID := nextNode.GetUID()
|
||||
|
||||
// 读取关联用户数据
|
||||
users := []interface{}{uid}
|
||||
userIDs := []string{}
|
||||
if last, ok := wflow["users"].([]interface{}); ok {
|
||||
users = last
|
||||
users = append(users, nextUID, uid)
|
||||
users = helper.ArrayUnique(users)
|
||||
}
|
||||
for _, u := range users {
|
||||
userIDs = append(userIDs, fmt.Sprintf("|%d|", u))
|
||||
}
|
||||
|
||||
// 更新数据
|
||||
mod := gou.Select("xiang.workflow")
|
||||
mod.Save(map[string]interface{}{
|
||||
"id": wflow["id"],
|
||||
"output": wflow["output"],
|
||||
"node_name": nextNode.Name,
|
||||
"node_status": "进行中",
|
||||
"user_id": nextUID,
|
||||
"users": users,
|
||||
"user_ids": strings.Join(userIDs, ","),
|
||||
})
|
||||
return workflow.Find(id)
|
||||
}
|
||||
|
||||
// GetUID 根据条件选择节点处理人
|
||||
func (node *Node) GetUID() int {
|
||||
res := gou.NewProcess(node.User.Process, node.User.Args...).Run()
|
||||
return any.Of(res).CInt()
|
||||
}
|
||||
|
||||
// nextNode 查找下一个节点
|
||||
func (workflow *WorkFlow) nextNode(currentNode string, data map[string]interface{}) *Node {
|
||||
next := -1
|
||||
for i, node := range workflow.Nodes {
|
||||
if node.Name == currentNode {
|
||||
next = i + 1
|
||||
break
|
||||
}
|
||||
}
|
||||
if next < 0 {
|
||||
exception.New("流程数据异常: 未找到工作流节点", 500).Ctx(currentNode).Throw()
|
||||
}
|
||||
data = maps.Of(data).Dot()
|
||||
for i := next; i < workflow.Len(); i++ {
|
||||
node := workflow.Nodes[i]
|
||||
if node.Conditions == nil || len(node.Conditions) == 0 {
|
||||
return &node
|
||||
}
|
||||
// 替换数据中的变量
|
||||
conditions := []helper.Condition{}
|
||||
for _, cond := range node.Conditions {
|
||||
if left, ok := cond.Left.(string); ok {
|
||||
cond.Left = gshare.Bind(left, data)
|
||||
}
|
||||
if right, ok := cond.Right.(string); ok {
|
||||
cond.Right = gshare.Bind(right, data)
|
||||
}
|
||||
conditions = append(conditions, cond)
|
||||
}
|
||||
if helper.When(conditions) {
|
||||
return &node
|
||||
}
|
||||
}
|
||||
|
||||
exception.New("流程数据异常: 未找到符合条件的工作流节点", 500).Ctx(currentNode).Throw()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (workflow *WorkFlow) isLastNode() {}
|
||||
|
||||
// Goto 工作流跳转
|
||||
func (workflow *WorkFlow) Goto(uid int, id int, node string, input map[string]interface{}) {}
|
||||
func (workflow *WorkFlow) Goto(uid int, id int, node string, output map[string]interface{}) {}
|
||||
|
||||
// Len 节点数量
|
||||
func (workflow *WorkFlow) Len() int {
|
||||
return len(workflow.Nodes)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,8 +5,10 @@ import (
|
|||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/kun/any"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/xiang/config"
|
||||
"github.com/yaoapp/xiang/flow"
|
||||
"github.com/yaoapp/xiang/model"
|
||||
"github.com/yaoapp/xiang/query"
|
||||
"github.com/yaoapp/xiang/share"
|
||||
|
|
@ -20,6 +22,7 @@ func init() {
|
|||
engineModels := path.Join(config.Conf.Source, "xiang", "models")
|
||||
model.LoadFrom(engineModels, "xiang.")
|
||||
query.Load(config.Conf)
|
||||
flow.Load(config.Conf)
|
||||
Load(config.Conf)
|
||||
}
|
||||
|
||||
|
|
@ -81,13 +84,13 @@ func TestSaveUpdate(t *testing.T) {
|
|||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func TestGet(t *testing.T) {
|
||||
func TestOpen(t *testing.T) {
|
||||
assignFlow := Select("assign")
|
||||
assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
})
|
||||
wflow := assignFlow.Get(1, 1)
|
||||
wflow := assignFlow.Open(1, 1)
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, int64(1), data.Get("id"))
|
||||
assert.Equal(t, "assign", data.Get("name"))
|
||||
|
|
@ -101,9 +104,9 @@ func TestGet(t *testing.T) {
|
|||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func TestGetEmpty(t *testing.T) {
|
||||
func TestOpenEmpty(t *testing.T) {
|
||||
assignFlow := Select("assign")
|
||||
wflow := assignFlow.Get(1, 1)
|
||||
wflow := assignFlow.Open(1, 1)
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, false, data.Has("id"))
|
||||
assert.Equal(t, "assign", data.Get("name"))
|
||||
|
|
@ -114,6 +117,61 @@ func TestGetEmpty(t *testing.T) {
|
|||
assert.Equal(t, []interface{}{1}, data.Get("users"))
|
||||
}
|
||||
|
||||
func TestNext(t *testing.T) {
|
||||
assignFlow := Select("assign")
|
||||
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
})
|
||||
wflow = assignFlow.Next(1, any.Of(wflow["id"]).CInt(), map[string]interface{}{
|
||||
"项目名称": "测试项目",
|
||||
"商务负责人名称": "林明波",
|
||||
})
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, int64(1), data.Get("id"))
|
||||
assert.Equal(t, "assign", data.Get("name"))
|
||||
assert.Equal(t, "项目负责人审批", data.Get("node_name"))
|
||||
assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
assert.Equal(t, "进行中", data.Get("status"))
|
||||
assert.Equal(t, int64(2), data.Get("user_id"))
|
||||
assert.Equal(t, true, data.Has("users"))
|
||||
assert.Equal(t, "测试项目", data.Get("output.项目名称"))
|
||||
assert.Equal(t, "林明波", data.Get("output.商务负责人名称"))
|
||||
|
||||
// 清理数据
|
||||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func TestNextWhen(t *testing.T) {
|
||||
assignFlow := Select("assign")
|
||||
wflow := assignFlow.Save(1, "选择商务负责人", 1, Input{
|
||||
Data: map[string]interface{}{"id": 1, "name": "云主机"},
|
||||
Form: map[string]interface{}{"biz_id": 1, "name": "张良明"},
|
||||
})
|
||||
id := any.Of(wflow["id"]).CInt()
|
||||
assignFlow.Next(1, id, map[string]interface{}{
|
||||
"项目名称": "测试项目",
|
||||
"商务负责人名称": "林明波",
|
||||
})
|
||||
|
||||
// Next When
|
||||
wflow = assignFlow.Next(2, id, map[string]interface{}{"审批结果": "通过"})
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, int64(1), data.Get("id"))
|
||||
assert.Equal(t, "assign", data.Get("name"))
|
||||
assert.Equal(t, "审批通过", data.Get("node_name"))
|
||||
assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
assert.Equal(t, "进行中", data.Get("status"))
|
||||
assert.Equal(t, int64(1), data.Get("user_id"))
|
||||
assert.Equal(t, true, data.Has("users"))
|
||||
assert.Equal(t, "测试项目", data.Get("output.项目名称"))
|
||||
assert.Equal(t, "林明波", data.Get("output.商务负责人名称"))
|
||||
assert.Equal(t, "通过", data.Get("output.审批结果"))
|
||||
|
||||
// 清理数据
|
||||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func check(t *testing.T) {
|
||||
keys := []string{}
|
||||
for key, workflow := range WorkFlows {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue