保存工作流

This commit is contained in:
Max 2021-11-26 17:16:48 +08:00
parent 668b33ffb7
commit ec007a0bba
5 changed files with 253 additions and 27 deletions

View file

@ -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")

View file

@ -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"
}
}
}

View file

@ -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"` // 表单数据
}

View file

@ -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
View 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))
}