保存工作流
This commit is contained in:
parent
668b33ffb7
commit
ec007a0bba
5 changed files with 253 additions and 27 deletions
|
|
@ -164,6 +164,10 @@ func (cfg *Config) SetDefaults() {
|
|||
cfg.RootPage = cfg.Root + "/pages"
|
||||
}
|
||||
|
||||
if cfg.RootWorkFlow == "" {
|
||||
cfg.RootWorkFlow = cfg.Root + "/workflows"
|
||||
}
|
||||
|
||||
if cfg.RootData == "" {
|
||||
cfg.RootData = cfg.Root + "/data"
|
||||
}
|
||||
|
|
@ -240,6 +244,7 @@ func SetAppPath(root string, envfile ...string) {
|
|||
Conf.RootTable = filepath.Join(fullpath, "/tables")
|
||||
Conf.RootChart = filepath.Join(fullpath, "/charts")
|
||||
Conf.RootPage = filepath.Join(fullpath, "/pages")
|
||||
Conf.RootWorkFlow = filepath.Join(fullpath, "/workflows")
|
||||
Conf.RootData = filepath.Join(fullpath, "/data")
|
||||
Conf.RootUI = filepath.Join(fullpath, "/ui")
|
||||
Conf.RootDB = filepath.Join(fullpath, "/db")
|
||||
|
|
|
|||
|
|
@ -1,30 +1,51 @@
|
|||
{
|
||||
"保存": {
|
||||
"label": "保存",
|
||||
"api": "/api/xiang/workflow/assign/save"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "保存",
|
||||
"api": "/api/xiang/workflow/assign/save"
|
||||
}
|
||||
},
|
||||
"提交": {
|
||||
"label": "提交",
|
||||
"api": "/api/xiang/workflow/assign/submit"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "提交",
|
||||
"api": "/api/xiang/workflow/assign/submit"
|
||||
}
|
||||
},
|
||||
"同意": {
|
||||
"label": "同意",
|
||||
"api": "/api/xiang/workflow/assign/resolve"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "同意",
|
||||
"api": "/api/xiang/workflow/assign/resolve"
|
||||
}
|
||||
},
|
||||
"驳回并重填": {
|
||||
"label": "驳回并重填",
|
||||
"api": "/api/xiang/workflow/assign/reject/reset"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "驳回并重填",
|
||||
"api": "/api/xiang/workflow/assign/reject/reset"
|
||||
}
|
||||
},
|
||||
"驳回并关闭": {
|
||||
"label": "驳回并关闭",
|
||||
"api": "/api/xiang/workflow/assign/reject/reset"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "驳回并关闭",
|
||||
"api": "/api/xiang/workflow/assign/reject/reset"
|
||||
}
|
||||
},
|
||||
"审批同意": {
|
||||
"label": "审批同意",
|
||||
"api": "/api/xiang/workflow/assign/terminal"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "审批同意",
|
||||
"api": "/api/xiang/workflow/assign/terminal"
|
||||
}
|
||||
},
|
||||
"关闭": {
|
||||
"label": "关闭",
|
||||
"api": "/api/xiang/workflow/terminal"
|
||||
"type": "button",
|
||||
"props": {
|
||||
"label": "关闭",
|
||||
"api": "/api/xiang/workflow/terminal"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,13 +4,14 @@ import "github.com/yaoapp/xiang/share"
|
|||
|
||||
// WorkFlow 工作流配置结构
|
||||
type WorkFlow struct {
|
||||
Name string `json:"-"`
|
||||
Source string `json:"-"`
|
||||
Version string `json:"version"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Decription string `json:"decription,omitempty"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
APIs []API `json:"apis"`
|
||||
Name string `json:"-"`
|
||||
Source []byte `json:"-"`
|
||||
Version string `json:"version"`
|
||||
Label string `json:"label,omitempty"`
|
||||
Decription string `json:"decription,omitempty"`
|
||||
Nodes []Node `json:"nodes"`
|
||||
APIs map[string]API `json:"apis"`
|
||||
Actions map[string]share.Render `json:"actions"`
|
||||
}
|
||||
|
||||
// Node 工作流节点
|
||||
|
|
@ -21,7 +22,7 @@ type Node struct {
|
|||
User User `json:"user,omitempty"`
|
||||
}
|
||||
|
||||
// User 工作相关用户读取条件
|
||||
// User 工作流相关用户读取条件
|
||||
type User struct {
|
||||
Process string `json:"process"`
|
||||
Args []interface{} `json:"args"`
|
||||
|
|
@ -33,3 +34,9 @@ type API struct {
|
|||
Process string `json:"process"`
|
||||
Args []interface{} `json:"args"`
|
||||
}
|
||||
|
||||
// Input 用户输入数据
|
||||
type Input struct {
|
||||
Data map[string]interface{} `json:"data"` // 记录数据
|
||||
Form map[string]interface{} `json:"form"` // 表单数据
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,15 @@
|
|||
package workflow
|
||||
|
||||
import "github.com/yaoapp/xiang/config"
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
"github.com/yaoapp/gou"
|
||||
"github.com/yaoapp/kun/exception"
|
||||
"github.com/yaoapp/xiang/config"
|
||||
"github.com/yaoapp/xiang/share"
|
||||
"github.com/yaoapp/xiang/xlog"
|
||||
)
|
||||
|
||||
// WorkFlows 工作流列表
|
||||
var WorkFlows = map[string]*WorkFlow{}
|
||||
|
|
@ -11,11 +20,57 @@ func Load(cfg config.Config) {
|
|||
}
|
||||
|
||||
// LoadFrom 从特定目录加载
|
||||
func LoadFrom(dir string, prefix string) {}
|
||||
func LoadFrom(dir string, prefix string) {
|
||||
if share.DirNotExists(dir) {
|
||||
return
|
||||
}
|
||||
share.Walk(dir, ".json", func(root, filename string) {
|
||||
name := prefix + share.SpecName(root, filename)
|
||||
content := share.ReadFile(filename)
|
||||
_, err := LoadWorkFlow(content, name)
|
||||
if err != nil {
|
||||
exception.New("%s 工作流格式错误", 400, name).Ctx(filename).Throw()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// LoadWorkFlow 载入工作流
|
||||
func LoadWorkFlow(source []byte, name string) (*WorkFlow, error) {
|
||||
workflow := WorkFlow{Name: name, Source: source}
|
||||
err := jsoniter.Unmarshal(source, &workflow)
|
||||
if err != nil {
|
||||
xlog.Println(name)
|
||||
xlog.Println(err.Error())
|
||||
xlog.Println(string(source))
|
||||
return nil, err
|
||||
}
|
||||
WorkFlows[workflow.Name] = &workflow
|
||||
return WorkFlows[workflow.Name], nil
|
||||
}
|
||||
|
||||
// Select 读取已加载图表
|
||||
func Select(name string) *WorkFlow {
|
||||
return WorkFlows[name]
|
||||
workflow, has := WorkFlows[name]
|
||||
if !has {
|
||||
exception.New(
|
||||
fmt.Sprintf("工作流:%s; 尚未加载", name),
|
||||
400,
|
||||
).Throw()
|
||||
}
|
||||
return workflow
|
||||
}
|
||||
|
||||
// Reload 重新载入工作流
|
||||
func (workflow *WorkFlow) Reload() *WorkFlow {
|
||||
new, err := LoadWorkFlow(workflow.Source, workflow.Name)
|
||||
if err != nil {
|
||||
exception.New(
|
||||
fmt.Sprintf("工作流:%s; 加载失败", workflow.Name),
|
||||
400,
|
||||
).Throw()
|
||||
}
|
||||
WorkFlows[workflow.Name] = new
|
||||
return new
|
||||
}
|
||||
|
||||
// Process
|
||||
|
|
@ -36,10 +91,60 @@ func (workflow *WorkFlow) Setting(id int) {}
|
|||
func (workflow *WorkFlow) SetupAPIs(id int) {}
|
||||
|
||||
// Get 读取当前工作流(未完成的)
|
||||
func (workflow *WorkFlow) Get(uid int, name string, dataID interface{}) {}
|
||||
func (workflow *WorkFlow) Get(uid int, name string, id interface{}) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{"*"},
|
||||
Wheres: []gou.QueryWhere{
|
||||
{Column: "name", Value: workflow.Name},
|
||||
{Column: "data_id", Value: id},
|
||||
{Column: "user_id", Value: uid},
|
||||
{Column: "status", Value: "进行中"},
|
||||
},
|
||||
}
|
||||
rows := wflow.MustGet(params)
|
||||
if len(rows) > 0 {
|
||||
return rows[0]
|
||||
}
|
||||
return map[string]interface{}{
|
||||
"name": workflow.Name,
|
||||
"data_id": id,
|
||||
"node_name": name,
|
||||
"user_id": uid,
|
||||
"status": "进行中",
|
||||
"node_status": "进行中",
|
||||
}
|
||||
}
|
||||
|
||||
// Save 保存工作流节点数据
|
||||
func (workflow *WorkFlow) Save(uid int, name string, node string, dataID interface{}, input map[string]interface{}) {
|
||||
func (workflow *WorkFlow) Save(uid int, name string, id interface{}, input Input) map[string]interface{} {
|
||||
wflow := gou.Select("xiang.workflow")
|
||||
params := gou.QueryParam{
|
||||
Select: []interface{}{"id"},
|
||||
Wheres: []gou.QueryWhere{
|
||||
{Column: "name", Value: workflow.Name},
|
||||
{Column: "data_id", Value: id},
|
||||
{Column: "user_id", Value: uid},
|
||||
{Column: "status", Value: "进行中"},
|
||||
},
|
||||
}
|
||||
rows := wflow.MustGet(params)
|
||||
data := map[string]interface{}{
|
||||
"name": workflow.Name,
|
||||
"data_id": id,
|
||||
"node_name": name,
|
||||
"user_id": uid,
|
||||
"input": input,
|
||||
}
|
||||
if len(rows) > 0 {
|
||||
data["id"] = rows[0].Get("id")
|
||||
} else {
|
||||
data["status"] = "进行中"
|
||||
data["node_status"] = "进行中"
|
||||
}
|
||||
|
||||
id = wflow.MustSave(data)
|
||||
return wflow.MustFind(id, gou.QueryParam{})
|
||||
}
|
||||
|
||||
// Next 下一个工作流
|
||||
|
|
|
|||
88
workflow/workflow_test.go
Normal file
88
workflow/workflow_test.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
package workflow
|
||||
|
||||
import (
|
||||
"path"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/yaoapp/kun/maps"
|
||||
"github.com/yaoapp/xiang/config"
|
||||
"github.com/yaoapp/xiang/model"
|
||||
"github.com/yaoapp/xiang/query"
|
||||
"github.com/yaoapp/xiang/share"
|
||||
"github.com/yaoapp/xun/capsule"
|
||||
)
|
||||
|
||||
func init() {
|
||||
share.DBConnect(config.Conf.Database)
|
||||
share.Load(config.Conf)
|
||||
model.Load(config.Conf)
|
||||
engineModels := path.Join(config.Conf.Source, "xiang", "models")
|
||||
model.LoadFrom(engineModels, "xiang.")
|
||||
query.Load(config.Conf)
|
||||
Load(config.Conf)
|
||||
}
|
||||
|
||||
func TestLoad(t *testing.T) {
|
||||
share.DBConnect(config.Conf.Database)
|
||||
share.Load(config.Conf)
|
||||
model.Load(config.Conf)
|
||||
query.Load(config.Conf)
|
||||
Load(config.Conf)
|
||||
LoadFrom("not a path", "404.")
|
||||
check(t)
|
||||
}
|
||||
|
||||
func TestSave(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": "张良明"},
|
||||
})
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, int64(1), data.Get("id"))
|
||||
assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
assert.Equal(t, "进行中", data.Get("status"))
|
||||
assert.Equal(t, float64(1), data.Get("input.data.id"))
|
||||
assert.Equal(t, "云主机", data.Get("input.data.name"))
|
||||
assert.Equal(t, float64(1), data.Get("input.form.biz_id"))
|
||||
assert.Equal(t, "张良明", data.Get("input.form.name"))
|
||||
// 清理数据
|
||||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func TestSaveUpdate(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.Save(1, "选择商务负责人", 1, Input{
|
||||
Data: map[string]interface{}{"id": 1, "name": "云存储"},
|
||||
Form: map[string]interface{}{"biz_id": 1, "name": "李明博"},
|
||||
})
|
||||
|
||||
data := maps.Of(wflow).Dot()
|
||||
assert.Equal(t, int64(1), data.Get("id"))
|
||||
assert.Equal(t, "选择商务负责人", data.Get("node_name"))
|
||||
assert.Equal(t, "进行中", data.Get("node_status"))
|
||||
assert.Equal(t, "进行中", data.Get("status"))
|
||||
assert.Equal(t, float64(1), data.Get("input.data.id"))
|
||||
assert.Equal(t, "云存储", data.Get("input.data.name"))
|
||||
assert.Equal(t, float64(1), data.Get("input.form.biz_id"))
|
||||
assert.Equal(t, "李明博", data.Get("input.form.name"))
|
||||
|
||||
// 清理数据
|
||||
capsule.Query().From("xiang_workflow").Truncate()
|
||||
}
|
||||
|
||||
func check(t *testing.T) {
|
||||
keys := []string{}
|
||||
for key, workflow := range WorkFlows {
|
||||
keys = append(keys, key)
|
||||
workflow.Reload()
|
||||
}
|
||||
assert.Equal(t, 1, len(keys))
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue