From 668b33ffb743b396ee71b71989ddcf891dd76b88 Mon Sep 17 00:00:00 2001 From: Max Date: Fri, 26 Nov 2021 14:22:36 +0800 Subject: [PATCH] =?UTF-8?q?[add]=20WorkFlow=20=E5=AE=9A=E4=B9=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- config/config.go | 34 ++--- flow/flow_test.go | 2 +- helper/map.go | 22 ++-- helper/map.process.go | 25 ++++ helper/process.go | 7 ++ main_test.go | 12 +- service/middleware.go | 2 +- table/table_test.go | 2 +- tests/flows/assign/reject/reset.flow.json | 43 +++++++ tests/flows/assign/reject/terminal.flow.json | 38 ++++++ tests/flows/assign/resolve.flow.json | 33 +++++ tests/flows/assign/save.flow.json | 24 ++++ tests/flows/assign/submit.flow.json | 29 +++++ tests/flows/assign/terminal.flow.json | 29 +++++ tests/flows/current/user.flow.json | 18 +++ tests/flows/select/manager.flow.json | 19 +++ tests/libs/assign.json | 30 +++++ tests/libs/column.json | 24 ++++ tests/tables/assign.tab.json | 16 +++ tests/workflows/assign.wflow.json | 83 +++++++++++++ workflow/types.go | 35 ++++++ workflow/workflow.go | 49 ++++++++ xiang/models/workflow.mod.json | 124 +++++++++++++++++++ 23 files changed, 661 insertions(+), 39 deletions(-) create mode 100644 helper/map.process.go create mode 100644 tests/flows/assign/reject/reset.flow.json create mode 100644 tests/flows/assign/reject/terminal.flow.json create mode 100644 tests/flows/assign/resolve.flow.json create mode 100644 tests/flows/assign/save.flow.json create mode 100644 tests/flows/assign/submit.flow.json create mode 100644 tests/flows/assign/terminal.flow.json create mode 100644 tests/flows/current/user.flow.json create mode 100644 tests/flows/select/manager.flow.json create mode 100644 tests/libs/assign.json create mode 100644 tests/tables/assign.tab.json create mode 100644 tests/workflows/assign.wflow.json create mode 100644 workflow/types.go create mode 100644 workflow/workflow.go create mode 100644 xiang/models/workflow.mod.json diff --git a/config/config.go b/config/config.go index c055ae7f..06389054 100644 --- a/config/config.go +++ b/config/config.go @@ -27,22 +27,24 @@ type Config struct { // XiangConfig 象传应用引擎配置 type XiangConfig struct { - Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test - Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据) - Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录 - Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录 - RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录 - RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录 - RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录 - RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录 - RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录 - RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录 - RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录 - RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录 - RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录 - RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录 - RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录 - RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录 + Mode string `json:"mode,omitempty" env:"XIANG_MODE" envDefault:"release"` // 象传引擎模式 debug/release/test + Source string `json:"source,omitempty" env:"XIANG_SOURCE" envDefault:"fs://."` // 源码路径(用于单元测试载入数据) + Path string `json:"path,omitempty" env:"XIANG_PATH" envDefault:"bin://xiang"` // 引擎文件目录 + Root string `json:"root,omitempty" env:"XIANG_ROOT" envDefault:"fs://."` // 应用文件目录 + RootUI string `json:"root_ui,omitempty" env:"XIANG_ROOT_UI"` // 应用界面静态文件目录 + RootDB string `json:"root_db,omitempty" env:"XIANG_ROOT_DB"` // 应用SQLite数据库目录 + RootData string `json:"root_data,omitempty" env:"XIANG_ROOT_DATA"` // 应用数据文件目录 + RootAPI string `json:"root_api,omitempty" env:"XIANG_ROOT_API"` // 应用API文件目录 + RootModel string `json:"root_model,omitempty" env:"XIANG_ROOT_MODEL"` // 应用模型文件目录 + RootFLow string `json:"root_flow,omitempty" env:"XIANG_ROOT_FLOW"` // 应用业务逻辑文件目录 + RootPlugin string `json:"root_plugin,omitempty" env:"XIANG_ROOT_PLUGIN"` // 应用业务插件文件目录 + RootLib string `json:"root_lib,omitempty" env:"XIANG_ROOT_LIB"` // 应用资料库文件目录 + RootTable string `json:"root_table,omitempty" env:"XIANG_ROOT_TABLE"` // 应用数据表格文件目录 + RootChart string `json:"root_chart,omitempty" env:"XIANG_ROOT_CHART"` // 应用分析图表文件目录 + RootPage string `json:"root_page,omitempty" env:"XIANG_ROOT_PAGE"` // 应用通用页面文件目录 + RootScreen string `json:"root_screen,omitempty" env:"XIANG_ROOT_SCREEN"` // 应用大屏文件目录 + RootForm string `json:"root_form,omitempty" env:"XIANG_ROOT_FORM"` // 表单文件目录 + RootWorkFlow string `json:"root_workflow,omitempty" env:"XIANG_ROOT_WORKFLOW"` // 工作流文件目录 } // ServiceConfig 服务配置 diff --git a/flow/flow_test.go b/flow/flow_test.go index 5231567e..29be2c33 100644 --- a/flow/flow_test.go +++ b/flow/flow_test.go @@ -20,5 +20,5 @@ func check(t *testing.T) { for key := range gou.Flows { keys = append(keys, key) } - assert.Equal(t, 2, len(keys)) + assert.Equal(t, 10, len(keys)) } diff --git a/helper/map.go b/helper/map.go index bf196ef9..21f6c792 100644 --- a/helper/map.go +++ b/helper/map.go @@ -1,8 +1,8 @@ package helper -import "github.com/yaoapp/gou" +import "github.com/yaoapp/kun/maps" -// MapValues 返回映射的数值 +// MapValues 返回映射表的数值 func MapValues(record map[string]interface{}) []interface{} { values := []interface{}{} for _, value := range record { @@ -11,7 +11,7 @@ func MapValues(record map[string]interface{}) []interface{} { return values } -// MapKeys 返回映射的键 +// MapKeys 返回映射表的键 func MapKeys(record map[string]interface{}) []string { keys := []string{} for key := range record { @@ -20,16 +20,8 @@ func MapKeys(record map[string]interface{}) []string { return keys } -// ProcessMapValues xiang.helper.MapValues 返回映射的数值 -func ProcessMapValues(process *gou.Process) interface{} { - process.ValidateArgNums(1) - record := process.ArgsMap(0) - return MapValues(record) -} - -// ProcessMapKeys xiang.helper.MapKeys 返回映射的键 -func ProcessMapKeys(process *gou.Process) interface{} { - process.ValidateArgNums(1) - record := process.ArgsMap(0) - return MapKeys(record) +// MapGet xiang.helper.MapGet 返回映射表给定键的值 +func MapGet(record map[string]interface{}, key string) interface{} { + data := maps.MapOf(record).Dot() + return data.Get(key) } diff --git a/helper/map.process.go b/helper/map.process.go new file mode 100644 index 00000000..9cd36ec8 --- /dev/null +++ b/helper/map.process.go @@ -0,0 +1,25 @@ +package helper + +import "github.com/yaoapp/gou" + +// ProcessMapValues xiang.helper.MapValues 返回映射表的数值 +func ProcessMapValues(process *gou.Process) interface{} { + process.ValidateArgNums(1) + record := process.ArgsMap(0) + return MapValues(record) +} + +// ProcessMapKeys xiang.helper.MapKeys 返回映射表的键 +func ProcessMapKeys(process *gou.Process) interface{} { + process.ValidateArgNums(1) + record := process.ArgsMap(0) + return MapKeys(record) +} + +// ProcessMapGet xiang.helper.MapKey 返回映射表给定键的值 +func ProcessMapGet(process *gou.Process) interface{} { + process.ValidateArgNums(2) + record := process.ArgsMap(0) + key := process.ArgsString(1) + return MapGet(record, key) +} diff --git a/helper/process.go b/helper/process.go index 06e29ff7..4bfb9806 100644 --- a/helper/process.go +++ b/helper/process.go @@ -12,17 +12,24 @@ func init() { gou.RegisterProcessHandler("xiang.helper.ArrayColumn", ProcessArrayColumn) gou.RegisterProcessHandler("xiang.helper.ArrayKeep", ProcessArrayKeep) gou.RegisterProcessHandler("xiang.helper.ArrayTree", ProcessArrayTree) + gou.RegisterProcessHandler("xiang.helper.MapKeys", ProcessMapKeys) gou.RegisterProcessHandler("xiang.helper.MapValues", ProcessMapValues) + gou.RegisterProcessHandler("xiang.helper.MapGet", ProcessMapGet) + gou.RegisterProcessHandler("xiang.helper.Captcha", ProcessCaptcha) gou.RegisterProcessHandler("xiang.helper.CaptchaValidate", ProcessCaptchaValidate) + gou.RegisterProcessHandler("xiang.helper.PasswordValidate", ProcessPasswordValidate) + gou.RegisterProcessHandler("xiang.helper.JwtMake", ProcessJwtMake) gou.RegisterProcessHandler("xiang.helper.JwtValidate", ProcessJwtValidate) + gou.RegisterProcessHandler("xiang.helper.For", ProcessFor) gou.RegisterProcessHandler("xiang.helper.Each", ProcessEach) gou.RegisterProcessHandler("xiang.helper.Case", ProcessCase) gou.RegisterProcessHandler("xiang.helper.IF", ProcessIF) + gou.RegisterProcessHandler("xiang.helper.Print", ProcessPrint) } diff --git a/main_test.go b/main_test.go index d3da2c32..fc7ae493 100644 --- a/main_test.go +++ b/main_test.go @@ -13,7 +13,6 @@ import ( "github.com/stretchr/testify/assert" "github.com/yaoapp/kun/any" "github.com/yaoapp/kun/maps" - "github.com/yaoapp/kun/utils" "github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/service" ) @@ -53,7 +52,7 @@ func TestCommandStart(t *testing.T) { // 发送请求 request := func() (maps.MapStr, error) { time.Sleep(time.Microsecond * 2000) - url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "local.iqka.com", config.Conf.Service.Port) + url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "127.0.0.1", config.Conf.Service.Port) // utils.Dump(url) resp, err := http.Get(url) if err != nil { @@ -70,11 +69,14 @@ func TestCommandStart(t *testing.T) { } // 等待服务启动 + time.Sleep(time.Second * 2) times := 0 for times < 20 { // 2秒超时 times++ + fmt.Printf("Trying(%d)...", times) res, err := request() if err != nil { + fmt.Printf(" %s\n", err.Error()) continue } assert.Equal(t, 1, any.Of(res.Get("id")).CInt()) @@ -98,8 +100,7 @@ func TestCommandStop(t *testing.T) { // 发送请求 request := func() (maps.MapStr, error) { time.Sleep(time.Microsecond * 2000) - url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "local.iqka.com", config.Conf.Service.Port) - utils.Dump(url) + url := fmt.Sprintf("http://%s:%d/api/user/find/1?select=id,name", "127.0.0.1", config.Conf.Service.Port) resp, err := http.Get(url) if err != nil { return nil, err @@ -115,6 +116,7 @@ func TestCommandStop(t *testing.T) { } // 等待服务启动 + time.Sleep(time.Second * 2) times := 0 for times < 20 { // 2秒超时 times++ @@ -127,7 +129,7 @@ func TestCommandStop(t *testing.T) { // 测试关闭 service.Stop(func() { log.Println("服务已关闭") }) - time.Sleep(time.Second * 2) + time.Sleep(time.Second * 5) _, err = request() assert.NotNil(t, err) return diff --git a/service/middleware.go b/service/middleware.go index 47254b63..025075fe 100644 --- a/service/middleware.go +++ b/service/middleware.go @@ -19,7 +19,7 @@ var AppFileServer http.Handler = http.FileServer(http.Dir(config.Conf.RootUI)) // Middlewares 服务中间件 var Middlewares = []gin.HandlerFunc{ - BindDomain, + // BindDomain, BinStatic, } diff --git a/table/table_test.go b/table/table_test.go index 11b18612..9ccf2389 100644 --- a/table/table_test.go +++ b/table/table_test.go @@ -48,5 +48,5 @@ func check(t *testing.T) { for key := range Tables { keys = append(keys, key) } - assert.Equal(t, 3, len(keys)) + assert.Equal(t, 4, len(keys)) } diff --git a/tests/flows/assign/reject/reset.flow.json b/tests/flows/assign/reject/reset.flow.json new file mode 100644 index 00000000..4b4088cd --- /dev/null +++ b/tests/flows/assign/reject/reset.flow.json @@ -0,0 +1,43 @@ +{ + "label": "审批驳回并重置状态", + "version": "1.0.0", + "description": "审批驳回并重置状态", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + }, + { + "name": "审批驳回", + "process": "xiang.workflow.Next", + "args": [ + "{{$res.当前用户.id}}", + "{{$res.工作流.id}}", + { "审批状态": "驳回", "驳回原因": "{{$input.reason}}" } + ] + }, + { + "name": "重置工作流", + "process": "xiang.workflow.Goto", + "args": [ + "{{$res.当前用户.id}}", + "{{$res.工作流.id}}", + "选择商务负责人", + {} + ] + } + ], + "output": "{{$res.重置工作流}}" +} diff --git a/tests/flows/assign/reject/terminal.flow.json b/tests/flows/assign/reject/terminal.flow.json new file mode 100644 index 00000000..3145ab88 --- /dev/null +++ b/tests/flows/assign/reject/terminal.flow.json @@ -0,0 +1,38 @@ +{ + "label": "审批驳回并关闭流程", + "version": "1.0.0", + "description": "审批驳回并关闭流程", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + }, + { + "name": "审批驳回", + "process": "xiang.workflow.Next", + "args": [ + "{{$res.当前用户.id}}", + "{{$res.工作流.id}}", + { "审批状态": "驳回", "驳回原因": "{{$input.reason}}" } + ] + }, + { + "name": "关闭流程", + "process": "xiang.workflow.Status", + "args": ["{{$res.工作流.id}}", "已关闭"] + } + ], + "output": "{{$res.关闭流程}}" +} diff --git a/tests/flows/assign/resolve.flow.json b/tests/flows/assign/resolve.flow.json new file mode 100644 index 00000000..d6b203a0 --- /dev/null +++ b/tests/flows/assign/resolve.flow.json @@ -0,0 +1,33 @@ +{ + "label": "指派信息审批通过", + "version": "1.0.0", + "description": "指派信息审批通过", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + }, + { + "name": "审批通过", + "process": "xiang.workflow.Next", + "args": [ + "{{$res.当前用户.id}}", + "{{$res.工作流.id}}", + { "审批状态": "通过" } + ] + } + ], + "output": "{{$res.审批通过}}" +} diff --git a/tests/flows/assign/save.flow.json b/tests/flows/assign/save.flow.json new file mode 100644 index 00000000..38173521 --- /dev/null +++ b/tests/flows/assign/save.flow.json @@ -0,0 +1,24 @@ +{ + "label": "保存指派信息", + "version": "1.0.0", + "description": "保存指派信息", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + } + ], + "output": "{{$res.工作流}}" +} diff --git a/tests/flows/assign/submit.flow.json b/tests/flows/assign/submit.flow.json new file mode 100644 index 00000000..294d1009 --- /dev/null +++ b/tests/flows/assign/submit.flow.json @@ -0,0 +1,29 @@ +{ + "label": "提交指派信息", + "version": "1.0.0", + "description": "提交指派信息", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + }, + { + "name": "提交审批", + "process": "xiang.workflow.Next", + "args": ["{{$res.当前用户.id}}", "{{$res.工作流.id}}"] + } + ], + "output": "{{$res.关闭流程}}" +} diff --git a/tests/flows/assign/terminal.flow.json b/tests/flows/assign/terminal.flow.json new file mode 100644 index 00000000..9bc4a82d --- /dev/null +++ b/tests/flows/assign/terminal.flow.json @@ -0,0 +1,29 @@ +{ + "label": "关闭流程", + "version": "1.0.0", + "description": "关闭流程", + "nodes": [ + { + "name": "当前用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "工作流", + "process": "xiang.workflow.Save", + "args": [ + "{{$res.当前用户.id}}", + "{{$name}}", + "{{$node}}", + "{{$id}}", + "{{$input}}" + ] + }, + { + "name": "关闭流程", + "process": "xiang.workflow.Status", + "args": ["{{$res.当前用户.id}}", "{{$res.工作流.id}}", "已关闭"] + } + ], + "output": "{{$res.关闭流程}}" +} diff --git a/tests/flows/current/user.flow.json b/tests/flows/current/user.flow.json new file mode 100644 index 00000000..61b16df9 --- /dev/null +++ b/tests/flows/current/user.flow.json @@ -0,0 +1,18 @@ +{ + "label": "当前用户", + "version": "1.0.0", + "description": "读取当前用户", + "nodes": [ + { + "name": "用户", + "process": "session.Get", + "args": ["user"] + }, + { + "name": "ID", + "process": "xiang.helper.MapGet", + "args": ["{{$res.用户}}", "ID"] + } + ], + "output": 1 +} diff --git a/tests/flows/select/manager.flow.json b/tests/flows/select/manager.flow.json new file mode 100644 index 00000000..73e990a2 --- /dev/null +++ b/tests/flows/select/manager.flow.json @@ -0,0 +1,19 @@ +{ + "label": "选择管理者", + "version": "1.0.0", + "description": "选择管理者", + "nodes": [ + { + "name": "负责人", + "engine": "xiang", + "query": { + "select": ["id"], + "from": "$xiang.user", + "wheres": [{ ":type": "类型", "=": "staff" }], + "first": true + }, + "orders": [":RAND()"] + } + ], + "output": "{{$res.负责人.id}}" +} diff --git a/tests/libs/assign.json b/tests/libs/assign.json new file mode 100644 index 00000000..7dcf8dad --- /dev/null +++ b/tests/libs/assign.json @@ -0,0 +1,30 @@ +{ + "保存": { + "label": "保存", + "api": "/api/xiang/workflow/assign/save" + }, + "提交": { + "label": "提交", + "api": "/api/xiang/workflow/assign/submit" + }, + "同意": { + "label": "同意", + "api": "/api/xiang/workflow/assign/resolve" + }, + "驳回并重填": { + "label": "驳回并重填", + "api": "/api/xiang/workflow/assign/reject/reset" + }, + "驳回并关闭": { + "label": "驳回并关闭", + "api": "/api/xiang/workflow/assign/reject/reset" + }, + "审批同意": { + "label": "审批同意", + "api": "/api/xiang/workflow/assign/terminal" + }, + "关闭": { + "label": "关闭", + "api": "/api/xiang/workflow/terminal" + } +} diff --git a/tests/libs/column.json b/tests/libs/column.json index 8e7d8b1b..4d34bcbc 100644 --- a/tests/libs/column.json +++ b/tests/libs/column.json @@ -39,5 +39,29 @@ "format": "YYYY年MM月DD日 @hh:mm:ss" } } + }, + "商务负责人": { + "label": "商务负责人", + "view": { + "name": "label", + "props": { + "value": ":kind.name" + } + }, + "edit": { + "type": "select", + "props": { + "value": ":kind.id", + "tree": true, + "searchable": true, + "remote": { + "api": "/api/kind/tree", + "query": { + "select": ["id", "name"], + "keyword": "where_name_like" + } + } + } + } } } diff --git a/tests/tables/assign.tab.json b/tests/tables/assign.tab.json new file mode 100644 index 00000000..db60aa71 --- /dev/null +++ b/tests/tables/assign.tab.json @@ -0,0 +1,16 @@ +{ + "name": "指派商务负责人", + "version": "1.0.0", + "decription": "指派商务负责人表单(?Next:表格和表单解耦)", + "bind": { "model": "service" }, + "edit": { + "primary": "id", + "layout": { + "fieldset": [ + { + "columns": [{ "name": "商务负责人", "width": 24 }] + } + ] + } + } +} diff --git a/tests/workflows/assign.wflow.json b/tests/workflows/assign.wflow.json new file mode 100644 index 00000000..c3702033 --- /dev/null +++ b/tests/workflows/assign.wflow.json @@ -0,0 +1,83 @@ +{ + "label": "指派商务负责人", + "version": "1.0.0", + "description": "指派商务负责人", + "apis": { + "/save": { + "name": "保存", + "process": "xiang.assign.save" + }, + "/terminal": { + "name": "关闭", + "process": "xiang.assign.terminal" + }, + "/submit": { + "name": "提交审批", + "process": "flows.assign.submit" + }, + "/reject/reset": { + "name": "审批驳回并重填", + "process": "flows.assign.reject.rest" + }, + "/reject/terminal": { + "name": "审批驳回并关闭", + "process": "flows.assign.reject.terminal" + }, + "/resolve": { + "name": "审批同意", + "process": "flows.assign.resolve" + } + }, + "actions": { + "关闭": { "@": "assign.关闭" }, + "保存": { "@": "assign.保存" }, + "提交": { "@": "assign.提交" }, + "同意": { "@": "assign.同意" }, + "驳回并重填": { "@": "assign.驳回并重填" }, + "驳回并关闭": { "@": "assign.驳回并关闭" } + }, + "nodes": [ + { + "name": "选择商务负责人", + "body": { + "type": "form", + "props": { "name": "assign" } + }, + "actions": ["关闭", "保存", "提交"], + "user": { "process": "flows.current.user", "args": ["id"] } + }, + { + "name": "项目负责人审批", + "body": { + "type": "iframe", + "props": { "src": "/workflows/assign/approve.html" } + }, + "actions": ["驳回并重填", "驳回并关闭", "同意"], + "user": { "process": "flows.select.manager", "args": ["id"] } + }, + { + "name": "审批通过", + "when": [{ "审批通过": "{{$data.审批状态}}", "=": "通过" }], + "body": { + "type": "markdown", + "props": { + "content": "审批通过, {{$data.项目名称}}商务负责人为: **{{$data.商务负责人名称}}**" + } + }, + "actions": ["关闭"], + "user": { "process": "flows.current.user", "args": ["id"] } + }, + { + "name": "审批驳回", + "when": [{ "审批通过": "{{$data.审批状态}}", "=": "驳回" }], + "body": { + "type": "markdown", + "props": { + "content": "您的请求被驳回, 驳回原因: **{{$data.驳回原因}}**" + } + }, + "actions": ["关闭"], + "user": { "process": "flows.current.user", "args": ["id"] } + } + ] +} diff --git a/workflow/types.go b/workflow/types.go new file mode 100644 index 00000000..ccc6646b --- /dev/null +++ b/workflow/types.go @@ -0,0 +1,35 @@ +package workflow + +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"` +} + +// Node 工作流节点 +type Node struct { + Name string `json:"name"` + Body share.Render `json:"body,omitempty"` + Actions []string `json:"actions,omitempty"` + User User `json:"user,omitempty"` +} + +// User 工作相关用户读取条件 +type User struct { + Process string `json:"process"` + Args []interface{} `json:"args"` +} + +// API 工作相关API +type API struct { + Name string `json:"name"` + Process string `json:"process"` + Args []interface{} `json:"args"` +} diff --git a/workflow/workflow.go b/workflow/workflow.go new file mode 100644 index 00000000..e94d68bf --- /dev/null +++ b/workflow/workflow.go @@ -0,0 +1,49 @@ +package workflow + +import "github.com/yaoapp/xiang/config" + +// WorkFlows 工作流列表 +var WorkFlows = map[string]*WorkFlow{} + +// Load 加载数据表格 +func Load(cfg config.Config) { + LoadFrom(cfg.RootWorkFlow, "") +} + +// LoadFrom 从特定目录加载 +func LoadFrom(dir string, prefix string) {} + +// Select 读取已加载图表 +func Select(name string) *WorkFlow { + return WorkFlows[name] +} + +// Process +// 读取工作流 xiang.workflow.Get(uid, name, data_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/<工作流名称>/setting +// 调用自定义API POST /api/xiang/workflow/<工作流名称>/<自定义API路由> + +// Setting 返回配置信息 +func (workflow *WorkFlow) Setting(id int) {} + +// SetupAPIs 注册API +func (workflow *WorkFlow) SetupAPIs(id int) {} + +// Get 读取当前工作流(未完成的) +func (workflow *WorkFlow) Get(uid int, name string, dataID interface{}) {} + +// Save 保存工作流节点数据 +func (workflow *WorkFlow) Save(uid int, name string, node string, dataID interface{}, input map[string]interface{}) { +} + +// Next 下一个工作流 +func (workflow *WorkFlow) Next(uid int, id int, input map[string]interface{}) {} + +// Goto 工作流跳转 +func (workflow *WorkFlow) Goto(uid int, id int, node string, input map[string]interface{}) {} diff --git a/xiang/models/workflow.mod.json b/xiang/models/workflow.mod.json new file mode 100644 index 00000000..69369f9b --- /dev/null +++ b/xiang/models/workflow.mod.json @@ -0,0 +1,124 @@ +{ + "name": "工作流", + "table": { + "name": "xiang_workflow", + "comment": "工作流数据表", + "engine": "InnoDB" + }, + "columns": [ + { "label": "ID", "name": "id", "type": "ID" }, + { + "label": "名称", + "comment": "工作流名称", + "name": "name", + "type": "string", + "length": 255, + "index": true, + "validations": [ + { + "method": "typeof", + "args": ["string"], + "message": "{{input}}类型错误, {{label}}应该为字符串" + } + ] + }, + { + "label": "数据标识", + "comment": "关联数据标识, 关联数据模型表格ID", + "name": "data_id", + "type": "bigInteger", + "index": true, + "nullable": true + }, + { + "label": "节点", + "comment": "当前节点名称", + "name": "node_name", + "type": "string", + "length": 255, + "index": true, + "nullable": true + }, + { + "label": "节点状态", + "comment": "当前节点状态", + "name": "node_status", + "type": "enum", + "default": "未开始", + "option": ["未开始", "进行中", "已完成"], + "index": true, + "validations": [ + { + "method": "typeof", + "args": ["string"], + "message": "{{input}}类型错误, {{label}}应该为字符串" + }, + { + "method": "enum", + "args": ["未开始", "进行中", "已完成"], + "message": "{{input}}不在许可范围, {{label}}应该为 未开始/进行中/已完成" + } + ] + }, + { + "label": "当前处理人", + "comment": "当前处理人", + "name": "user_id", + "type": "bigInteger", + "index": true, + "nullable": true + }, + { + "label": "处理人", + "comment": "处理人选取条件", + "name": "user", + "type": "json", + "nullable": true + }, + { + "label": "数据", + "comment": "各节点输入数据(Key-Value)", + "name": "input", + "type": "json", + "nullable": true + }, + { + "label": "处理结果", + "comment": "各节点处理结果(Key-Value)", + "name": "output", + "type": "json", + "nullable": true + }, + { + "label": "操作记录", + "comment": "操作记录", + "name": "history", + "type": "json", + "nullable": true + }, + + { + "label": "状态", + "comment": "流程状态", + "name": "status", + "type": "enum", + "default": "未开始", + "option": ["未开始", "进行中", "已完成", "已关闭"], + "index": true, + "validations": [ + { + "method": "typeof", + "args": ["string"], + "message": "{{input}}类型错误, {{label}}应该为字符串" + }, + { + "method": "enum", + "args": ["未开始", "进行中", "已完成", "已关闭"], + "message": "{{input}}不在许可范围, {{label}}应该为 未开始/进行中/已完成/已关闭" + } + ] + } + ], + "indexes": [], + "option": { "timestamps": true, "soft_deletes": true } +}