From 903cf7f516bbb09e925f29230c52ea548d9b457f Mon Sep 17 00:00:00 2001 From: lulz1 Date: Fri, 15 Nov 2024 16:53:35 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=87=E4=BB=BD=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../flow_instance/api_instances_create.go | 8 +- .../flow_instance/api_instances_query.go | 6 +- .../flow_instance/api_instances_query_list.go | 8 +- .../flow_instance/api_instances_start.go | 8 +- .../flow_instance/api_instances_steps_add.go | 8 +- .../api_instances_steps_revert.go | 8 +- .../api_instances_steps_update.go | 8 +- app/http/swagger/docs.go | 507 ++++++++++++++++++ app/http/swagger/swagger.json | 507 ++++++++++++++++++ app/http/swagger/swagger.yaml | 340 ++++++++++++ app/provider/abstract/connect/node.go | 302 +++++++++++ app/provider/abstract/node.go | 182 ------- .../flow_definition/model_approval_flow.go | 82 +-- .../flow_definition/model_approval_path.go | 59 +- .../flow_definition/model_approval_step.go | 47 +- app/provider/flow_definition/model_test.go | 2 + app/provider/flow_definition/service.go | 3 +- .../flow_instance/model/approval_instance.go | 219 ++++---- .../model/dynamic_path_config.go | 28 - .../flow_instance/model/iInstance_step.go | 72 --- .../model/instance_path_config.go | 59 ++ .../flow_instance/model/instance_step.go | 101 ++++ app/provider/flow_instance/service.go | 3 +- app/utils/convert.go | 21 + 24 files changed, 2102 insertions(+), 486 deletions(-) create mode 100644 app/provider/abstract/connect/node.go delete mode 100644 app/provider/abstract/node.go delete mode 100644 app/provider/flow_instance/model/dynamic_path_config.go delete mode 100644 app/provider/flow_instance/model/iInstance_step.go create mode 100644 app/provider/flow_instance/model/instance_path_config.go create mode 100644 app/provider/flow_instance/model/instance_step.go diff --git a/app/http/module/flow_instance/api_instances_create.go b/app/http/module/flow_instance/api_instances_create.go index 7995699..3861b82 100644 --- a/app/http/module/flow_instance/api_instances_create.go +++ b/app/http/module/flow_instance/api_instances_create.go @@ -18,13 +18,13 @@ type InstancesCreateParam struct { // InstancesCreate handler // @Summary 创建流程实例 // @Description 通过流程模板创建流程实例 -// @ID Instances-Create -// @Tags Instances-Create +// @ID instances-create +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesCreateParam body InstancesCreateParam true "创建参数" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/create [post] func (api *FlowInstanceApi) InstancesCreate(c *gin.Context) { param := utils.QuickBind[InstancesCreateParam](c) diff --git a/app/http/module/flow_instance/api_instances_query.go b/app/http/module/flow_instance/api_instances_query.go index 84a2b27..4a67e22 100644 --- a/app/http/module/flow_instance/api_instances_query.go +++ b/app/http/module/flow_instance/api_instances_query.go @@ -15,12 +15,12 @@ type InstancesQueryParam struct { // @Summary 实例查询 // @Description 通过实例的ID查询流程实例详情 // @ID instances-query -// @Tags instances-query +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesQueryParam body InstancesQueryParam true "查询参数" -// @Success 200 {object} result.Result{data = ApprovalInstanceDTO} "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result{data=ApprovalInstanceDTO} "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/query [post] func (api *FlowInstanceApi) InstancesQuery(c *gin.Context) { param := utils.QuickBind[InstancesQueryParam](c) diff --git a/app/http/module/flow_instance/api_instances_query_list.go b/app/http/module/flow_instance/api_instances_query_list.go index 342f866..ba117a4 100644 --- a/app/http/module/flow_instance/api_instances_query_list.go +++ b/app/http/module/flow_instance/api_instances_query_list.go @@ -15,13 +15,13 @@ type InstancesQueryListParam struct { // InstancesQueryList handler // @Summary 输入你的接口总结 // @Description 输入你的接口总结详情 -// @ID 接口Id -// @Tags 接口tag +// @ID instances-query-list +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesQueryListParam body InstancesQueryListParam true "输入参数描述" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/query/list [post] func (api *FlowInstanceApi) InstancesQueryList(c *gin.Context) { param := utils.QuickBind[InstancesQueryListParam](c) diff --git a/app/http/module/flow_instance/api_instances_start.go b/app/http/module/flow_instance/api_instances_start.go index c6aaec7..6c27527 100644 --- a/app/http/module/flow_instance/api_instances_start.go +++ b/app/http/module/flow_instance/api_instances_start.go @@ -14,13 +14,13 @@ type InstancesStartParam struct { // InstancesStart handler // @Summary 启动审批流 // @Description 启动审批流 -// @ID Instances-Start -// @Tags Instances-Start +// @ID instances-start +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesStartParam body InstancesStartParam true "启动实例ID" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/start [post] func (api *FlowInstanceApi) InstancesStart(c *gin.Context) { param := utils.QuickBind[InstancesStartParam](c) diff --git a/app/http/module/flow_instance/api_instances_steps_add.go b/app/http/module/flow_instance/api_instances_steps_add.go index 25d3546..a835acb 100644 --- a/app/http/module/flow_instance/api_instances_steps_add.go +++ b/app/http/module/flow_instance/api_instances_steps_add.go @@ -10,13 +10,13 @@ type InstancesStepsAddParam struct { // InstancesStepsAdd handler // @Summary 输入你的接口总结 // @Description 输入你的接口总结详情 -// @ID 接口Id -// @Tags 接口tag +// @ID instances-steps-add +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesStepsAddParam body InstancesStepsAddParam true "输入参数描述" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/steps/add [post] func (api *FlowInstanceApi) InstancesStepsAdd(c *gin.Context) { // TODO: Implement InstancesStepsAdd diff --git a/app/http/module/flow_instance/api_instances_steps_revert.go b/app/http/module/flow_instance/api_instances_steps_revert.go index 1c37f14..d40fc47 100644 --- a/app/http/module/flow_instance/api_instances_steps_revert.go +++ b/app/http/module/flow_instance/api_instances_steps_revert.go @@ -9,13 +9,13 @@ type InstancesStepsRevertParam struct{} // InstancesStepsRevert handler // @Summary 输入你的接口总结 // @Description 输入你的接口总结详情 -// @ID 接口Id -// @Tags 接口tag +// @ID instances-steps-revert +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesStepsRevertParam body InstancesStepsRevertParam true "输入参数描述" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/steps/revert [post] func (api *FlowInstanceApi) InstancesStepsRevert(c *gin.Context) { // TODO: Implement InstancesStepsRevert diff --git a/app/http/module/flow_instance/api_instances_steps_update.go b/app/http/module/flow_instance/api_instances_steps_update.go index 372e0bd..b33d1e2 100644 --- a/app/http/module/flow_instance/api_instances_steps_update.go +++ b/app/http/module/flow_instance/api_instances_steps_update.go @@ -9,13 +9,13 @@ type InstancesStepsUpdateParam struct{} // InstancesStepsUpdate handler // @Summary 输入你的接口总结 // @Description 输入你的接口总结详情 -// @ID 接口Id -// @Tags 接口tag +// @ID instances-steps-update +// @Tags flow-instances // @Accept json // @Produce json // @Param InstancesStepsUpdateParam body InstancesStepsUpdateParam true "输入参数描述" -// @Success 200 {object} result.Result "返回成功的流程定义数据" -// @Failure 500 {object} result.Result "返回失败的流程定义数据" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" // @Router /instances/steps/update [post] func (api *FlowInstanceApi) InstancesStepsUpdate(c *gin.Context) { // TODO: Implement InstancesStepsUpdate diff --git a/app/http/swagger/docs.go b/app/http/swagger/docs.go index e55e048..5f3faca 100644 --- a/app/http/swagger/docs.go +++ b/app/http/swagger/docs.go @@ -231,6 +231,305 @@ const docTemplate = `{ } } } + }, + "/instances/create": { + "post": { + "description": "通过流程模板创建流程实例", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "创建流程实例", + "operationId": "instances-create", + "parameters": [ + { + "description": "创建参数", + "name": "InstancesCreateParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesCreateParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/query": { + "post": { + "description": "通过实例的ID查询流程实例详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "实例查询", + "operationId": "instances-query", + "parameters": [ + { + "description": "查询参数", + "name": "InstancesQueryParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesQueryParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/base.Result" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/flow_instance.ApprovalInstanceDTO" + } + } + } + ] + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/query/list": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-query-list", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesQueryListParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesQueryListParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/start": { + "post": { + "description": "启动审批流", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "启动审批流", + "operationId": "instances-start", + "parameters": [ + { + "description": "启动实例ID", + "name": "InstancesStartParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStartParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/add": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-add", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsAddParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsAddParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/revert": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-revert", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsRevertParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsRevertParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/update": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-update", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsUpdateParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsUpdateParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } } }, "definitions": { @@ -340,6 +639,214 @@ const docTemplate = `{ "type": "string" } } + }, + "flow_instance.ApprovalInstanceDTO": { + "type": "object", + "properties": { + "approver_id": { + "description": "申请人ID", + "type": "string" + }, + "creator_id": { + "description": "创建者ID", + "type": "string" + }, + "current_step_ids": { + "description": "当前步骤ID", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.CurrentStepDTO" + } + }, + "dynamic_path_configs": { + "description": "动态路径配置,自定义,不直接存储", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.DynamicPathConfigDTO" + } + }, + "flow_id": { + "description": "流程ID", + "type": "integer" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "status": { + "description": "审批状态", + "type": "string" + }, + "steps": { + "description": "实例步骤", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.InstanceStepDTO" + } + } + } + }, + "flow_instance.ApprovalRecordDTO": { + "type": "object", + "properties": { + "approver_id": { + "description": "审批人ID", + "type": "string" + }, + "comments": { + "description": "审批意见", + "type": "string" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_step_id": { + "description": "关联的步骤实例ID", + "type": "integer" + }, + "is_timeout": { + "description": "是否超时", + "type": "boolean" + }, + "status": { + "description": "审批状态", + "type": "string" + } + } + }, + "flow_instance.CurrentStepDTO": { + "type": "object", + "properties": { + "current_step_id": { + "type": "integer" + }, + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.DynamicPathConfigDTO": { + "type": "object", + "properties": { + "from_step_id": { + "description": "来源步骤ID", + "type": "integer" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_id": { + "description": "关联的审批实例ID", + "type": "integer" + }, + "is_parallel": { + "description": "是否并行", + "type": "boolean" + }, + "priority": { + "description": "路径优先级", + "type": "integer" + }, + "to_step_id": { + "description": "目标步骤ID", + "type": "integer" + } + } + }, + "flow_instance.InstanceStepDTO": { + "type": "object", + "properties": { + "approver_comments": { + "description": "审批意见", + "type": "string" + }, + "approver_id": { + "description": "审批人ID", + "type": "string" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_id": { + "description": "所属审批实例ID", + "type": "integer" + }, + "is_dynamic": { + "description": "是否为动态步骤", + "type": "boolean" + }, + "records": { + "description": "审批记录", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.ApprovalRecordDTO" + } + }, + "status": { + "description": "审批状态", + "type": "string" + }, + "step_id": { + "description": "关联的流程步骤ID", + "type": "integer" + } + } + }, + "flow_instance.InstancesCreateParam": { + "type": "object", + "properties": { + "applicant_key": { + "description": "申请人ID", + "type": "string" + }, + "creator_key": { + "type": "string" + }, + "flow_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesQueryListParam": { + "type": "object", + "properties": { + "page_number": { + "description": "当前页码,默认是第一页", + "type": "integer" + }, + "page_size": { + "description": "每页记录数,默认是10条", + "type": "integer" + } + } + }, + "flow_instance.InstancesQueryParam": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStartParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsAddParam": { + "type": "object" + }, + "flow_instance.InstancesStepsRevertParam": { + "type": "object" + }, + "flow_instance.InstancesStepsUpdateParam": { + "type": "object" } }, "securityDefinitions": { diff --git a/app/http/swagger/swagger.json b/app/http/swagger/swagger.json index 2076c01..af7cdcf 100644 --- a/app/http/swagger/swagger.json +++ b/app/http/swagger/swagger.json @@ -224,6 +224,305 @@ } } } + }, + "/instances/create": { + "post": { + "description": "通过流程模板创建流程实例", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "创建流程实例", + "operationId": "instances-create", + "parameters": [ + { + "description": "创建参数", + "name": "InstancesCreateParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesCreateParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/query": { + "post": { + "description": "通过实例的ID查询流程实例详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "实例查询", + "operationId": "instances-query", + "parameters": [ + { + "description": "查询参数", + "name": "InstancesQueryParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesQueryParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/base.Result" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/flow_instance.ApprovalInstanceDTO" + } + } + } + ] + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/query/list": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-query-list", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesQueryListParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesQueryListParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/start": { + "post": { + "description": "启动审批流", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "启动审批流", + "operationId": "instances-start", + "parameters": [ + { + "description": "启动实例ID", + "name": "InstancesStartParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStartParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/add": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-add", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsAddParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsAddParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/revert": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-revert", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsRevertParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsRevertParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/update": { + "post": { + "description": "输入你的接口总结详情", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "输入你的接口总结", + "operationId": "instances-steps-update", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsUpdateParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsUpdateParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } } }, "definitions": { @@ -333,6 +632,214 @@ "type": "string" } } + }, + "flow_instance.ApprovalInstanceDTO": { + "type": "object", + "properties": { + "approver_id": { + "description": "申请人ID", + "type": "string" + }, + "creator_id": { + "description": "创建者ID", + "type": "string" + }, + "current_step_ids": { + "description": "当前步骤ID", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.CurrentStepDTO" + } + }, + "dynamic_path_configs": { + "description": "动态路径配置,自定义,不直接存储", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.DynamicPathConfigDTO" + } + }, + "flow_id": { + "description": "流程ID", + "type": "integer" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "status": { + "description": "审批状态", + "type": "string" + }, + "steps": { + "description": "实例步骤", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.InstanceStepDTO" + } + } + } + }, + "flow_instance.ApprovalRecordDTO": { + "type": "object", + "properties": { + "approver_id": { + "description": "审批人ID", + "type": "string" + }, + "comments": { + "description": "审批意见", + "type": "string" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_step_id": { + "description": "关联的步骤实例ID", + "type": "integer" + }, + "is_timeout": { + "description": "是否超时", + "type": "boolean" + }, + "status": { + "description": "审批状态", + "type": "string" + } + } + }, + "flow_instance.CurrentStepDTO": { + "type": "object", + "properties": { + "current_step_id": { + "type": "integer" + }, + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.DynamicPathConfigDTO": { + "type": "object", + "properties": { + "from_step_id": { + "description": "来源步骤ID", + "type": "integer" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_id": { + "description": "关联的审批实例ID", + "type": "integer" + }, + "is_parallel": { + "description": "是否并行", + "type": "boolean" + }, + "priority": { + "description": "路径优先级", + "type": "integer" + }, + "to_step_id": { + "description": "目标步骤ID", + "type": "integer" + } + } + }, + "flow_instance.InstanceStepDTO": { + "type": "object", + "properties": { + "approver_comments": { + "description": "审批意见", + "type": "string" + }, + "approver_id": { + "description": "审批人ID", + "type": "string" + }, + "id": { + "description": "主键ID", + "type": "integer" + }, + "instance_id": { + "description": "所属审批实例ID", + "type": "integer" + }, + "is_dynamic": { + "description": "是否为动态步骤", + "type": "boolean" + }, + "records": { + "description": "审批记录", + "type": "array", + "items": { + "$ref": "#/definitions/flow_instance.ApprovalRecordDTO" + } + }, + "status": { + "description": "审批状态", + "type": "string" + }, + "step_id": { + "description": "关联的流程步骤ID", + "type": "integer" + } + } + }, + "flow_instance.InstancesCreateParam": { + "type": "object", + "properties": { + "applicant_key": { + "description": "申请人ID", + "type": "string" + }, + "creator_key": { + "type": "string" + }, + "flow_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesQueryListParam": { + "type": "object", + "properties": { + "page_number": { + "description": "当前页码,默认是第一页", + "type": "integer" + }, + "page_size": { + "description": "每页记录数,默认是10条", + "type": "integer" + } + } + }, + "flow_instance.InstancesQueryParam": { + "type": "object", + "properties": { + "id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStartParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsAddParam": { + "type": "object" + }, + "flow_instance.InstancesStepsRevertParam": { + "type": "object" + }, + "flow_instance.InstancesStepsUpdateParam": { + "type": "object" } }, "securityDefinitions": { diff --git a/app/http/swagger/swagger.yaml b/app/http/swagger/swagger.yaml index 8d0164a..aed8790 100644 --- a/app/http/swagger/swagger.yaml +++ b/app/http/swagger/swagger.yaml @@ -73,6 +73,152 @@ definitions: description: 流程名称 type: string type: object + flow_instance.ApprovalInstanceDTO: + properties: + approver_id: + description: 申请人ID + type: string + creator_id: + description: 创建者ID + type: string + current_step_ids: + description: 当前步骤ID + items: + $ref: '#/definitions/flow_instance.CurrentStepDTO' + type: array + dynamic_path_configs: + description: 动态路径配置,自定义,不直接存储 + items: + $ref: '#/definitions/flow_instance.DynamicPathConfigDTO' + type: array + flow_id: + description: 流程ID + type: integer + id: + description: 主键ID + type: integer + status: + description: 审批状态 + type: string + steps: + description: 实例步骤 + items: + $ref: '#/definitions/flow_instance.InstanceStepDTO' + type: array + type: object + flow_instance.ApprovalRecordDTO: + properties: + approver_id: + description: 审批人ID + type: string + comments: + description: 审批意见 + type: string + id: + description: 主键ID + type: integer + instance_step_id: + description: 关联的步骤实例ID + type: integer + is_timeout: + description: 是否超时 + type: boolean + status: + description: 审批状态 + type: string + type: object + flow_instance.CurrentStepDTO: + properties: + current_step_id: + type: integer + instance_id: + type: integer + type: object + flow_instance.DynamicPathConfigDTO: + properties: + from_step_id: + description: 来源步骤ID + type: integer + id: + description: 主键ID + type: integer + instance_id: + description: 关联的审批实例ID + type: integer + is_parallel: + description: 是否并行 + type: boolean + priority: + description: 路径优先级 + type: integer + to_step_id: + description: 目标步骤ID + type: integer + type: object + flow_instance.InstanceStepDTO: + properties: + approver_comments: + description: 审批意见 + type: string + approver_id: + description: 审批人ID + type: string + id: + description: 主键ID + type: integer + instance_id: + description: 所属审批实例ID + type: integer + is_dynamic: + description: 是否为动态步骤 + type: boolean + records: + description: 审批记录 + items: + $ref: '#/definitions/flow_instance.ApprovalRecordDTO' + type: array + status: + description: 审批状态 + type: string + step_id: + description: 关联的流程步骤ID + type: integer + type: object + flow_instance.InstancesCreateParam: + properties: + applicant_key: + description: 申请人ID + type: string + creator_key: + type: string + flow_id: + type: integer + type: object + flow_instance.InstancesQueryListParam: + properties: + page_number: + description: 当前页码,默认是第一页 + type: integer + page_size: + description: 每页记录数,默认是10条 + type: integer + type: object + flow_instance.InstancesQueryParam: + properties: + id: + type: integer + type: object + flow_instance.InstancesStartParam: + properties: + instance_id: + type: integer + type: object + flow_instance.InstancesStepsAddParam: + type: object + flow_instance.InstancesStepsRevertParam: + type: object + flow_instance.InstancesStepsUpdateParam: + type: object info: contact: email: yejianfeng @@ -214,6 +360,200 @@ paths: summary: 添加步骤到流程 tags: - flow-definition + /instances/create: + post: + consumes: + - application/json + description: 通过流程模板创建流程实例 + operationId: instances-create + parameters: + - description: 创建参数 + in: body + name: InstancesCreateParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesCreateParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 创建流程实例 + tags: + - flow-instances + /instances/query: + post: + consumes: + - application/json + description: 通过实例的ID查询流程实例详情 + operationId: instances-query + parameters: + - description: 查询参数 + in: body + name: InstancesQueryParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesQueryParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + allOf: + - $ref: '#/definitions/base.Result' + - properties: + data: + $ref: '#/definitions/flow_instance.ApprovalInstanceDTO' + type: object + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 实例查询 + tags: + - flow-instances + /instances/query/list: + post: + consumes: + - application/json + description: 输入你的接口总结详情 + operationId: instances-query-list + parameters: + - description: 输入参数描述 + in: body + name: InstancesQueryListParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesQueryListParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 输入你的接口总结 + tags: + - flow-instances + /instances/start: + post: + consumes: + - application/json + description: 启动审批流 + operationId: instances-start + parameters: + - description: 启动实例ID + in: body + name: InstancesStartParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStartParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 启动审批流 + tags: + - flow-instances + /instances/steps/add: + post: + consumes: + - application/json + description: 输入你的接口总结详情 + operationId: instances-steps-add + parameters: + - description: 输入参数描述 + in: body + name: InstancesStepsAddParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsAddParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 输入你的接口总结 + tags: + - flow-instances + /instances/steps/revert: + post: + consumes: + - application/json + description: 输入你的接口总结详情 + operationId: instances-steps-revert + parameters: + - description: 输入参数描述 + in: body + name: InstancesStepsRevertParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsRevertParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 输入你的接口总结 + tags: + - flow-instances + /instances/steps/update: + post: + consumes: + - application/json + description: 输入你的接口总结详情 + operationId: instances-steps-update + parameters: + - description: 输入参数描述 + in: body + name: InstancesStepsUpdateParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsUpdateParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 输入你的接口总结 + tags: + - flow-instances securityDefinitions: ApiKeyAuth: in: header diff --git a/app/provider/abstract/connect/node.go b/app/provider/abstract/connect/node.go new file mode 100644 index 0000000..94ba7b5 --- /dev/null +++ b/app/provider/abstract/connect/node.go @@ -0,0 +1,302 @@ +package connect + +import ( + "approveflow/app/base" + "encoding/json" + "fmt" +) + +type NodeConnectTest struct { + Nodes []*Node + NodeMap map[string]*Node // 用于快速查找节点 +} + +type NodeManager interface { + GetNodes() []AbstractNode + SetNodes([]AbstractNode) + GetNodeMap() map[string]AbstractNode + NewNodePathConfig(fromNodeKey, toNodeKey string) AbstractNodePathConfig +} + +type NodeManagerS struct { + NodeManager +} + +type AbstractNode interface { + GetPathConfigs() []AbstractNodePathConfig + SetPathConfigs([]AbstractNodePathConfig) + GetKey() string +} + +type AbstractNodePathConfig interface { + GetKey() string + GetNodeID() int64 + GetFromNodeKey() string + GetToNodeKey() string +} + +type Node struct { + PathConfigs []AbstractNodePathConfig `gorm:"-" json:"path_configs"` + base.Model +} + +func (n *Node) MarshalBinary() ([]byte, error) { + return json.Marshal(n) +} + +func (n *Node) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, n) +} + +func (n *Node) GetPathConfigs() []AbstractNodePathConfig { + return n.PathConfigs +} + +func (n *Node) SetPathConfigs(configs []AbstractNodePathConfig) { + n.PathConfigs = configs +} + +func (n *Node) GetKey() string { + return n.Key +} + +type NodePathConfig struct { + NodeID int64 `gorm:"type:bigint;index;not null" json:"node_id"` + FromNodeKey string `gorm:"type:varchar(50);index;not null" json:"from_node_key"` + ToNodeKey string ` gorm:"type:varchar(50);index;not null" json:"to_node_key"` + base.Model +} + +func (n *NodePathConfig) MarshalBinary() ([]byte, error) { + return json.Marshal(n) +} + +func (n *NodePathConfig) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, n) +} + +func (n NodePathConfig) GetKey() string { + return n.Key +} + +func (n NodePathConfig) GetNodeID() int64 { + return n.NodeID +} + +func (n NodePathConfig) GetFromNodeKey() string { + return n.FromNodeKey +} + +func (n NodePathConfig) GetToNodeKey() string { + return n.ToNodeKey +} + +func (s *NodeManagerS) InitializeNodeMap() { + InitializeNodeMap(s) +} + +func InitializeNodeMap(nct NodeManager) { + for _, node := range nct.GetNodes() { + nct.GetNodeMap()[node.GetKey()] = node + } +} + +func GetNextNodes(nct NodeManager, node AbstractNode) ([]AbstractNode, error) { + // 获取所有节点和路径配置 + nodeMap := nct.GetNodeMap() + pathConfigs := node.GetPathConfigs() + // 存储下一个节点 + var nextNodes []AbstractNode + // 遍历当前节点的路径配置 + for _, pathConfig := range pathConfigs { + if pathConfig.GetFromNodeKey() == node.GetKey() { + // 根据路径配置的 ToNodeKey 找到目标节点 + nextNodeKey := pathConfig.GetToNodeKey() + if nextNode, exists := nodeMap[nextNodeKey]; exists { + nextNodes = append(nextNodes, nextNode) + } else { + return nil, fmt.Errorf("next node with key %s not found", nextNodeKey) + } + } + } + return nextNodes, nil +} + +func (s *NodeManagerS) InsertNodeFirst(newNode AbstractNode) *NodeManagerS { + if s.GetNodes() == nil { + s.SetNodes([]AbstractNode{newNode}) + } + s.SetNodes(append(s.GetNodes(), newNode)) + return s +} + +func InsertNodeFirst(nct NodeManager, newNode AbstractNode) { + if nct.GetNodes() == nil { + nct.SetNodes([]AbstractNode{newNode}) + } + nct.SetNodes(append(nct.GetNodes(), newNode)) +} + +func (s *NodeManagerS) InsertNodeBetween(fromNode, toNode, newNode AbstractNode) error { + return InsertNodeBetween(s, fromNode, toNode, newNode) +} + +// InsertNodeBetween 在两个节点之间插入新节点 +func InsertNodeBetween(nct NodeManager, fromNode, toNode, newNode AbstractNode) error { + if fromNode == nil || toNode == nil || newNode == nil { + return fmt.Errorf("节点不能为空") + } + + // 移除从 fromNode 到 toNode 的路径 + removePath(nct, fromNode, toNode) + + // 添加从 fromNode 到 newNode 的路径 + addPath(nct, fromNode, newNode) + + // 添加从 newNode 到 toNode 的路径 + addPath(nct, newNode, toNode) + + // 如果新节点不在节点列表中,添加进去 + if _, exists := nct.GetNodeMap()[newNode.GetKey()]; !exists { + nct.SetNodes(append(nct.GetNodes(), newNode)) + nct.GetNodeMap()[newNode.GetKey()] = newNode + } + + return nil +} + +func (s *NodeManagerS) InsertNodeAfter(node, newNode AbstractNode) error { + return InsertNodeAfter(s, node, newNode) +} + +// InsertNodeAfter 在指定节点之后插入节点 +func InsertNodeAfter(nct NodeManager, node, newNode AbstractNode) error { + if node == nil || newNode == nil { + return fmt.Errorf("节点不能为空") + } + + // 获取从当前节点出去的所有节点 + outgoingNodes := getOutgoingNodes(nct, node) + + // 移除这些路径并重新连接 + for _, toNode := range outgoingNodes { + removePath(nct, node, toNode) + addPath(nct, newNode, toNode) + } + + // 添加从当前节点到新节点的路径 + addPath(nct, node, newNode) + + // 如果新节点不在节点列表中,添加进去 + if _, exists := nct.GetNodeMap()[newNode.GetKey()]; !exists { + nct.SetNodes(append(nct.GetNodes(), newNode)) + nct.GetNodeMap()[newNode.GetKey()] = newNode + } + + return nil +} + +func (s *NodeManagerS) InsertNodeBefore(node, newNode AbstractNode) error { + return InsertNodeBefore(s, node, newNode) +} + +// InsertNodeBefore 在指定节点之前插入节点 +func InsertNodeBefore(nct NodeManager, node, newNode AbstractNode) error { + if node == nil || newNode == nil { + return fmt.Errorf("节点不能为空") + } + + // 获取指向当前节点的所有节点 + incomingNodes := getIncomingNodes(nct, node) + + // 移除这些路径并重新连接 + for _, fromNode := range incomingNodes { + removePath(nct, fromNode, node) + addPath(nct, fromNode, newNode) + } + + // 添加从新节点到当前节点的路径 + addPath(nct, newNode, node) + + // 如果新节点不在节点列表中,添加进去 + if _, exists := nct.GetNodeMap()[newNode.GetKey()]; !exists { + nct.SetNodes(append(nct.GetNodes(), newNode)) + nct.GetNodeMap()[newNode.GetKey()] = newNode + } + + return nil +} + +func (s *NodeManagerS) VerifyConnections() error { + return VerifyConnections(s) +} + +// VerifyConnections 验证节点连接 +func VerifyConnections(nct NodeManager) error { + for _, node := range nct.GetNodes() { + for _, pc := range node.GetPathConfigs() { + if _, exists := nct.GetNodeMap()[pc.GetFromNodeKey()]; !exists { + return fmt.Errorf("FromNodeKey %s 不存在", pc.GetFromNodeKey()) + } + if _, exists := nct.GetNodeMap()[pc.GetToNodeKey()]; !exists { + return fmt.Errorf("ToNodeKey %s 不存在", pc.GetToNodeKey()) + } + } + } + return nil +} + +func (s *NodeManagerS) PrintConnections() { + PrintConnections(s) +} + +// PrintConnections 打印连接情况 +func PrintConnections(nct NodeManager) { + fmt.Println("节点连接情况:") + for _, node := range nct.GetNodes() { + for _, pc := range node.GetPathConfigs() { + fmt.Printf("节点 %s -> 节点 %s\n", pc.GetFromNodeKey(), pc.GetToNodeKey()) + } + } +} + +// addPath 添加路径 +func addPath(nct NodeManager, fromNode, toNode AbstractNode) { + pathConfig := nct.NewNodePathConfig(fromNode.GetKey(), toNode.GetKey()) + fromNode.SetPathConfigs(append(fromNode.GetPathConfigs(), pathConfig)) +} + +// removePath 移除路径 +func removePath(nct NodeManager, fromNode, toNode AbstractNode) { + var newPathConfigs []AbstractNodePathConfig + for _, pc := range fromNode.GetPathConfigs() { + if !(pc.GetFromNodeKey() == fromNode.GetKey() && pc.GetToNodeKey() == toNode.GetKey()) { + newPathConfigs = append(newPathConfigs, pc) + } + } + fromNode.SetPathConfigs(newPathConfigs) +} + +// getOutgoingNodes 获取后继节点 +func getOutgoingNodes(nct NodeManager, node AbstractNode) []AbstractNode { + var result []AbstractNode + for _, pc := range node.GetPathConfigs() { + if toNode, exists := nct.GetNodeMap()[pc.GetToNodeKey()]; exists { + result = append(result, toNode) + } + } + return result +} + +// getIncomingNodes 获取前驱节点 +func getIncomingNodes(nct NodeManager, node AbstractNode) []AbstractNode { + var result []AbstractNode + for _, n := range nct.GetNodes() { + for _, pc := range n.GetPathConfigs() { + if pc.GetToNodeKey() == node.GetKey() { + result = append(result, n) + } + } + } + return result +} diff --git a/app/provider/abstract/node.go b/app/provider/abstract/node.go deleted file mode 100644 index adf3895..0000000 --- a/app/provider/abstract/node.go +++ /dev/null @@ -1,182 +0,0 @@ -package abstract - -import ( - "approveflow/app/base" - "fmt" -) - -type NodeConnectTest struct { - Nodes []*Node - NodeMap map[string]*Node // 用于快速查找节点 -} - -type NodeManager interface { - GetNodes() []*Node - SetNodes([]*Node) - GetNodeMap() map[string]*Node -} - -type Node struct { - PathConfigs []*NodePathConfig `gorm:"foreignKey:NodeID;constraint:OnDelete:CASCADE" json:"path_configs"` - base.Model -} - -type NodePathConfig struct { - NodeID int64 `gorm:"type:bigint;index;not null" json:"node_id"` - FromNodeKey string `gorm:"type:varchar(50);index;not null" json:"from_node_key"` - ToNodeKey string ` gorm:"type:varchar(50);index;not null" json:"to_node_key"` - base.Model -} - -func InitializeNodeMap(nct NodeManager) { - for _, node := range nct.GetNodes() { - nct.GetNodeMap()[node.Key] = node - } -} - -// InsertNodeBetween 在两个节点之间插入新节点 -func InsertNodeBetween(nct NodeManager, fromNode, toNode, newNode *Node) error { - if fromNode == nil || toNode == nil || newNode == nil { - return fmt.Errorf("节点不能为空") - } - - // 移除从 fromNode 到 toNode 的路径 - removePath(nct, fromNode, toNode) - - // 添加从 fromNode 到 newNode 的路径 - addPath(nct, fromNode, newNode) - - // 添加从 newNode 到 toNode 的路径 - addPath(nct, newNode, toNode) - - // 如果新节点不在节点列表中,添加进去 - if _, exists := nct.GetNodeMap()[newNode.Key]; !exists { - nct.SetNodes(append(nct.GetNodes(), newNode)) - nct.GetNodeMap()[newNode.Key] = newNode - } - - return nil -} - -// InsertNodeAfter 在指定节点之后插入节点 -func InsertNodeAfter(nct NodeManager, node, newNode *Node) error { - if node == nil || newNode == nil { - return fmt.Errorf("节点不能为空") - } - - // 获取从当前节点出去的所有节点 - outgoingNodes := getOutgoingNodes(nct, node) - - // 移除这些路径并重新连接 - for _, toNode := range outgoingNodes { - removePath(nct, node, toNode) - addPath(nct, newNode, toNode) - } - - // 添加从当前节点到新节点的路径 - addPath(nct, node, newNode) - - // 如果新节点不在节点列表中,添加进去 - if _, exists := nct.GetNodeMap()[newNode.Key]; !exists { - nct.SetNodes(append(nct.GetNodes(), newNode)) - nct.GetNodeMap()[newNode.Key] = newNode - } - - return nil -} - -// InsertNodeBefore 在指定节点之前插入节点 -func InsertNodeBefore(nct NodeManager, node, newNode *Node) error { - if node == nil || newNode == nil { - return fmt.Errorf("节点不能为空") - } - - // 获取指向当前节点的所有节点 - incomingNodes := getIncomingNodes(nct, node) - - // 移除这些路径并重新连接 - for _, fromNode := range incomingNodes { - removePath(nct, fromNode, node) - addPath(nct, fromNode, newNode) - } - - // 添加从新节点到当前节点的路径 - addPath(nct, newNode, node) - - // 如果新节点不在节点列表中,添加进去 - if _, exists := nct.GetNodeMap()[newNode.Key]; !exists { - nct.SetNodes(append(nct.GetNodes(), newNode)) - nct.GetNodeMap()[newNode.Key] = newNode - } - - return nil -} - -// VerifyConnections 验证节点连接 -func VerifyConnections(nct NodeManager) error { - for _, node := range nct.GetNodes() { - for _, pc := range node.PathConfigs { - if _, exists := nct.GetNodeMap()[pc.FromNodeKey]; !exists { - return fmt.Errorf("FromNodeKey %s 不存在", pc.FromNodeKey) - } - if _, exists := nct.GetNodeMap()[pc.ToNodeKey]; !exists { - return fmt.Errorf("ToNodeKey %s 不存在", pc.ToNodeKey) - } - } - } - return nil -} - -// PrintConnections 打印连接情况 -func PrintConnections(nct NodeManager) { - fmt.Println("节点连接情况:") - for _, node := range nct.GetNodes() { - for _, pc := range node.PathConfigs { - fmt.Printf("节点 %s -> 节点 %s\n", pc.FromNodeKey, pc.ToNodeKey) - } - } -} - -// addPath 添加路径 -func addPath(nct NodeManager, fromNode, toNode *Node) { - pathConfig := &NodePathConfig{ - FromNodeKey: fromNode.Key, - ToNodeKey: toNode.Key, - } - fromNode.PathConfigs = append(fromNode.PathConfigs, pathConfig) -} - -// removePath 移除路径 -func removePath(nct NodeManager, fromNode, toNode *Node) { - var newPathConfigs []*NodePathConfig - for _, pc := range fromNode.PathConfigs { - if !(pc.FromNodeKey == fromNode.Key && pc.ToNodeKey == toNode.Key) { - newPathConfigs = append(newPathConfigs, pc) - } - } - fromNode.PathConfigs = newPathConfigs -} - -// getOutgoingNodes 获取后继节点 -func getOutgoingNodes(nct NodeManager, node *Node) []*Node { - var result []*Node - for _, pc := range node.PathConfigs { - if toNode, exists := nct.GetNodeMap()[pc.ToNodeKey]; exists { - result = append(result, toNode) - } - } - return result -} - -// getIncomingNodes 获取前驱节点 -func getIncomingNodes(nct NodeManager, node *Node) []*Node { - var result []*Node - for _, n := range nct.GetNodes() { - for _, pc := range n.PathConfigs { - if pc.ToNodeKey == node.Key { - result = append(result, n) - } - } - } - return result -} diff --git a/app/provider/flow_definition/model_approval_flow.go b/app/provider/flow_definition/model_approval_flow.go index 83191a5..1520cf4 100644 --- a/app/provider/flow_definition/model_approval_flow.go +++ b/app/provider/flow_definition/model_approval_flow.go @@ -2,17 +2,52 @@ package flow_definition import ( "approveflow/app/base" + "approveflow/app/provider/abstract/connect" + "approveflow/app/utils" "encoding/json" "fmt" ) // ApprovalFlow 审批流程表 type ApprovalFlow struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - Name string `gorm:"type:varchar(100);not null" json:"name"` // 流程名称 - Description string `gorm:"type:text" json:"description"` // 流程描述 - Steps []*ApprovalStep `gorm:"foreignKey:FlowID;constraint:OnDelete:CASCADE" json:"steps"` // 流程步骤 - base.Model // 通用字段,包括创建时间、更新时间等 + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + Name string `gorm:"type:varchar(100);not null" json:"name"` // 流程名称 + Description string `gorm:"type:text" json:"description"` // 流程描述 + Steps []*ApprovalStep `gorm:"foreignKey:FlowID;constraint:OnDelete:CASCADE" json:"steps"` // 流程步骤 + NodeMap map[string]connect.AbstractNode `gorm:"-" json:"node_map"` // 流程步骤 + base.Model // 通用字段,包括创建时间、更新时间等 +} + +func (flow *ApprovalFlow) GetNodes() []connect.AbstractNode { + return utils.ConvertToAbstractNodes(flow.Steps, func(step *ApprovalStep) connect.AbstractNode { + return step + }) +} + +func (flow *ApprovalFlow) SetNodes(nodes []connect.AbstractNode) { + steps := make([]*ApprovalStep, len(nodes)) + for i, node := range nodes { + // 类型断言,将 abstract.AbstractNode 转换为 *ApprovalStep + if step, ok := node.(*ApprovalStep); ok { + steps[i] = step + } else { + // 如果转换失败,可以选择处理错误,抛出异常或记录日志 + panic("node is not of type *ApprovalStep") + } + } + flow.Steps = steps +} + +func (flow *ApprovalFlow) GetNodeMap() map[string]connect.AbstractNode { + if flow.NodeMap == nil { + flow.NodeMap = map[string]connect.AbstractNode{} + connect.InitializeNodeMap(flow) + } + return flow.NodeMap +} + +func (flow *ApprovalFlow) NewNodePathConfig(fromNodeKey, toNodeKey string) connect.AbstractNodePathConfig { + return NewPathConfig(fromNodeKey, toNodeKey) } func (flow *ApprovalFlow) MarshalBinary() ([]byte, error) { @@ -31,10 +66,13 @@ func NewApprovalFlow(name string, description string) *ApprovalFlow { } startStep := NewStartStep() endStep := NewEndStep() - path := NewPathBetween(startStep, endStep) - err := startStep.AddPathConfig(path) - err = approvalFlow.AddStep(startStep) - err = approvalFlow.AddStep(endStep) + + connect.InsertNodeFirst(approvalFlow, startStep) + err := connect.InsertNodeAfter(approvalFlow, startStep, endStep) + //path := NewPathBetween(startStep, endStep) + //err := startStep.AddPathConfig(path) + //err = approvalFlow.AddStep(startStep) + //err = approvalFlow.AddStep(endStep) if err != nil { return nil } @@ -115,31 +153,9 @@ func (flow *ApprovalFlow) Validate() error { } // FirstStep 获取审批流程中的第一个审批节点 -func (flow *ApprovalFlow) FirstStep(data map[string]interface{}) (*ApprovalStep, error) { - // 首先检查是否有步骤 - if len(flow.Steps) == 0 { - return nil, fmt.Errorf("no steps found in the approval flow") - } - // 创建一个 map 用于存储所有指向目标节点的 StepKey - toStepKeys := make(map[string]bool) +func (flow *ApprovalFlow) FirstStep() (*ApprovalStep, error) { for _, step := range flow.Steps { - for _, pathConfig := range step.PathConfigs { - condition, err := pathConfig.EvaluateCondition(data) - if err != nil { - continue - } - if !condition { - continue - } - // 使用 pathConfig.ToStepKey 替换 ToStepID,并且存储为 string 类型 - toStepKeys[pathConfig.ToStepKey] = true - } - } - // 遍历步骤,找到第一个没有被指向的步骤 - for _, step := range flow.Steps { - // 假设 step.Key 是 string 类型,检查是否存在于 toStepKeys 中 - if _, exists := toStepKeys[step.Key]; !exists { - // 如果当前步骤的 Key 不在 toStepKeys 中,则它是第一个审批节点 + if step.StepCode == StepStart { return step, nil } } diff --git a/app/provider/flow_definition/model_approval_path.go b/app/provider/flow_definition/model_approval_path.go index ecdb45e..fa6e9a4 100644 --- a/app/provider/flow_definition/model_approval_path.go +++ b/app/provider/flow_definition/model_approval_path.go @@ -10,15 +10,30 @@ import ( // ApprovalPathConfig 审批路径配置表 type ApprovalPathConfig struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - FromStepID int64 `gorm:"index;not null" json:"from_step_id"` // 来源步骤ID - FromStepKey string `gorm:"type:varchar(50);index;not null" json:"from_step_key"` // 来源步骤ID - - ToStepKey string `gorm:"type:varchar(50);index;not null" json:"to_step_key"` // 目标步骤ID - IsParallel bool `gorm:"type:bool;default:false" json:"is_parallel"` // 是否并行 - ConditionExpression string `gorm:"type:text" json:"condition_expression"` // 条件表达式 + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + NodeID int64 `gorm:"type:bigint;index;not null" json:"node_id"` + FromNodeKey string `gorm:"type:varchar(50);index;not null" json:"from_node_key"` + ToNodeKey string ` gorm:"type:varchar(50);index;not null" json:"to_node_key"` + IsParallel bool `gorm:"type:bool;default:false" json:"is_parallel"` // 是否并行 + ConditionExpression string `gorm:"type:text" json:"condition_expression"` // 条件表达式 + base.Model ApprovalConditionCommon `gorm:"-"` - base.Model // 通用字段,包括创建时间、更新时间等 +} + +func (a *ApprovalPathConfig) GetKey() string { + return a.Key +} + +func (a *ApprovalPathConfig) GetNodeID() int64 { + return a.NodeID +} + +func (a *ApprovalPathConfig) GetFromNodeKey() string { + return a.FromNodeKey +} + +func (a *ApprovalPathConfig) GetToNodeKey() string { + return a.ToNodeKey } func (a *ApprovalPathConfig) MarshalBinary() ([]byte, error) { @@ -31,23 +46,31 @@ func (a *ApprovalPathConfig) UnmarshalBinary(data []byte) error { func (a *ApprovalPathConfig) NewPathWithStep(step *ApprovalStep) (*ApprovalPathConfig, *ApprovalPathConfig) { toPath := &ApprovalPathConfig{ - FromStepID: step.ID, - FromStepKey: step.Key, - ToStepKey: a.ToStepKey, - IsParallel: false, + IsParallel: false, } - a.ToStepKey = step.Key + a.FromNodeKey = step.Key + a.NodeID = step.ID + a.ToNodeKey = step.Key return a, toPath } func NewPathBetween(fromStep, toStep *ApprovalStep) *ApprovalPathConfig { - return &ApprovalPathConfig{ - FromStepID: fromStep.ID, - FromStepKey: fromStep.Key, - //ToStepID: toStep.ID, - ToStepKey: toStep.Key, + path := &ApprovalPathConfig{ IsParallel: false, } + path.FromNodeKey = fromStep.Key + path.NodeID = fromStep.ID + path.ToNodeKey = toStep.Key + return path +} + +func NewPathConfig(fromStepKey, toStepKey string) *ApprovalPathConfig { + path := &ApprovalPathConfig{ + IsParallel: false, + } + path.FromNodeKey = fromStepKey + path.ToNodeKey = toStepKey + return path } func NewPathWithTarget(target *ApprovalStep) *ApprovalPathConfig { diff --git a/app/provider/flow_definition/model_approval_step.go b/app/provider/flow_definition/model_approval_step.go index 6679762..0824c14 100644 --- a/app/provider/flow_definition/model_approval_step.go +++ b/app/provider/flow_definition/model_approval_step.go @@ -2,6 +2,8 @@ package flow_definition import ( "approveflow/app/base" + "approveflow/app/provider/abstract/connect" + "approveflow/app/utils" "encoding/json" "fmt" "github.com/google/uuid" @@ -10,14 +12,37 @@ import ( // ApprovalStep 审批步骤表 type ApprovalStep struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - FlowID int64 `gorm:"index;not null" json:"flow_id"` // 所属流程ID - Name string `gorm:"type:varchar(100);not null" json:"name"` // 步骤名称 - StepCode string `gorm:"type:varchar(100)" json:"step_code"` // 步骤编号用于标识特殊的审批节点 - Rules []*ApprovalRule `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"rules"` // Foreign key for Rules - DynamicConfig *DynamicApprovalStepConfig `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"dynamic_config"` // One-to-one with DynamicConfig - PathConfigs []*ApprovalPathConfig `gorm:"foreignKey:FromStepID;constraint:OnDelete:CASCADE" json:"from_path_configs"` // 路径配置集合 - base.Model // 通用字段,包括创建时间、更新时间等 + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + FlowID int64 `gorm:"index;not null" json:"flow_id"` // 所属流程ID + Name string `gorm:"type:varchar(100);not null" json:"name"` // 步骤名称 + StepCode string `gorm:"type:varchar(100)" json:"step_code"` // 步骤编号用于标识特殊的审批节点 + Rules []*ApprovalRule `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"rules"` // Foreign key for Rules + DynamicConfig *DynamicApprovalStepConfig `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"dynamic_config"` // One-to-one with DynamicConfig + PathConfigs []*ApprovalPathConfig `gorm:"foreignKey:NodeID;constraint:OnDelete:CASCADE" json:"path_configs"` // 路径配置集合 + base.Model +} + +func (step *ApprovalStep) GetPathConfigs() []connect.AbstractNodePathConfig { + abstractConfigs := make([]connect.AbstractNodePathConfig, len(step.PathConfigs)) + for i, config := range step.PathConfigs { + abstractConfigs[i] = config // 直接赋值,因为 ApprovalPathConfig 实现了 AbstractNodePathConfig + } + return abstractConfigs +} + +func (step *ApprovalStep) SetPathConfigs(configs []connect.AbstractNodePathConfig) { + convertedConfigs, err := utils.ConvertToSpecificType(configs, func(item connect.AbstractNodePathConfig) (*ApprovalPathConfig, bool) { + specificConfig, ok := item.(*ApprovalPathConfig) + return specificConfig, ok + }) + if err != nil { + panic(fmt.Sprintf("Error in SetPathConfigs: %v", err)) + } + step.PathConfigs = convertedConfigs +} + +func (step *ApprovalStep) GetKey() string { + return step.Key } func (step *ApprovalStep) MarshalBinary() ([]byte, error) { @@ -105,7 +130,7 @@ func (step *ApprovalStep) AddPathConfig(pathConfig *ApprovalPathConfig) error { return fmt.Errorf("path config with ID %d already exists in step", pathConfig.ID) } } - pathConfig.FromStepID = step.ID + pathConfig.NodeID = step.ID step.PathConfigs = append(step.PathConfigs, pathConfig) return nil } @@ -130,7 +155,7 @@ func (step *ApprovalStep) GetNextStepIDs(data map[string]interface{}) ([]string, return nil, err } if met { - nextStepKeys = append(nextStepKeys, pathConfig.ToStepKey) + nextStepKeys = append(nextStepKeys, pathConfig.ToNodeKey) if !pathConfig.IsParallel { break // 非并行时,只取第一个满足条件的路径 } @@ -141,7 +166,7 @@ func (step *ApprovalStep) GetNextStepIDs(data map[string]interface{}) ([]string, func (step *ApprovalStep) GetPathByToKey(toStepKey string) (*ApprovalPathConfig, bool) { for _, pathConfig := range step.PathConfigs { - if pathConfig.ToStepKey == toStepKey { + if pathConfig.ToNodeKey == toStepKey { return pathConfig, true } } diff --git a/app/provider/flow_definition/model_test.go b/app/provider/flow_definition/model_test.go index d05c563..0075c8e 100644 --- a/app/provider/flow_definition/model_test.go +++ b/app/provider/flow_definition/model_test.go @@ -19,4 +19,6 @@ func TestModel(t *testing.T) { base.BaseExtensionField{}, ApprovalStep{}, ApprovalRule{}) + + db.AutoMigrate(ApprovalStep{}) } diff --git a/app/provider/flow_definition/service.go b/app/provider/flow_definition/service.go index aa6657f..cfde215 100644 --- a/app/provider/flow_definition/service.go +++ b/app/provider/flow_definition/service.go @@ -41,6 +41,7 @@ func (f *FlowDefinitionService) AddStepWithPosition(ctx context.Context, flowID return f.handlerFlow(ctx, flowID, func(ctx context.Context, flow *ApprovalFlow) error { fromStep, err := flow.GetStepByKey(fromStepId) _, err = flow.GetStepByKey(toStepKey) + if err != nil { return err } @@ -202,7 +203,7 @@ func (f *FlowDefinitionService) UpdatePathConfig(ctx context.Context, flowID int if err != nil { return err } - pathById.ToStepKey = config.ToStepKey + pathById.ToNodeKey = config.ToNodeKey pathById.IsParallel = config.IsParallel pathById.ConditionExpression = config.ConditionExpression return nil diff --git a/app/provider/flow_instance/model/approval_instance.go b/app/provider/flow_instance/model/approval_instance.go index c63fe21..b84fd06 100644 --- a/app/provider/flow_instance/model/approval_instance.go +++ b/app/provider/flow_instance/model/approval_instance.go @@ -2,7 +2,9 @@ package model import ( "approveflow/app/base" + "approveflow/app/provider/abstract/connect" "approveflow/app/provider/flow_definition" + "approveflow/app/utils" "context" "encoding/json" "fmt" @@ -20,20 +22,39 @@ const ( // ApprovalInstance 审批实例表 type ApprovalInstance struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - FlowID int64 `gorm:"type:bigint;index;not null" json:"flow_id"` // 流程ID - ApplicantKey string `gorm:"type:bigint;index;not null" json:"approver_id"` //申请人ID - CreatorKey string `gorm:"type:bigint;not null" json:"creator_id"` // 创建者ID - Status string `gorm:"type:varchar(50);not null" json:"status"` // 审批状态 - CurrentStepIDs []*CurrentStep `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"current_step_ids"` // 当前步骤ID - Steps []*InstanceStep `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"steps"` // 实例步骤 - DynamicPathConfigs []*DynamicPathConfig `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"dynamic_path_configs"` // 动态路径配置,自定义,不直接存储 - Data string `gorm:"type:json" json:"data"` // 保存审批数据的 JSON - ApprovalFlow *flow_definition.ApprovalFlow `gorm:"-" json:"approval_flow"` + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + FlowID int64 `gorm:"type:bigint;index;not null" json:"flow_id"` // 流程ID + ApplicantKey string `gorm:"type:bigint;index;not null" json:"approver_id"` //申请人ID + CreatorKey string `gorm:"type:bigint;not null" json:"creator_id"` // 创建者ID + Status string `gorm:"type:varchar(50);not null" json:"status"` // 审批状态 + CurrentStepIDs []*CurrentStep `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"current_step_ids"` // 当前步骤ID + Steps []*InstanceStep `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"steps"` // 实例步骤 + Data string `gorm:"type:json" json:"data"` // 保存审批数据的 JSON sync.RWMutex base.Model } +func (instance *ApprovalInstance) GetNodes() []connect.AbstractNode { + return utils.ConvertToAbstractNodes(instance.Steps, func(step *InstanceStep) connect.AbstractNode { + return step + }) +} + +func (instance *ApprovalInstance) SetNodes(nodes []connect.AbstractNode) { + //TODO implement me + panic("implement me") +} + +func (instance *ApprovalInstance) GetNodeMap() map[string]connect.AbstractNode { + //TODO implement me + panic("implement me") +} + +func (instance *ApprovalInstance) NewNodePathConfig(fromNodeKey, toNodeKey string) connect.AbstractNodePathConfig { + //TODO implement me + panic("implement me") +} + // CurrentStep 目前审批中的节点 type CurrentStep struct { CurrentStepId int64 `gorm:"type:bigint;index;not null" json:"current_step_id"` @@ -51,27 +72,68 @@ func NewApprovalInstance( return nil, err } instance := &ApprovalInstance{ - FlowID: approvalFlow.ID, - ApplicantKey: data[base.ApplicantKey].(string), - CreatorKey: data[base.CreatorKey].(string), - Steps: []*InstanceStep{}, - DynamicPathConfigs: []*DynamicPathConfig{}, - Data: string(bytes), + FlowID: approvalFlow.ID, + ApplicantKey: data[base.ApplicantKey].(string), + CreatorKey: data[base.CreatorKey].(string), + Steps: []*InstanceStep{}, + Data: string(bytes), } - - // 从审批模板创建 审批节点 - for _, step := range approvalFlow.Steps { - approver, err := step.DynamicConfig.GetApprover(ctx, data) - if err != nil { - return nil, err - } - instanceStep := NewInstanceStep(step.ID, step.Name, approver.Key, false) - instance.Steps = append(instance.Steps, instanceStep) + // Get the first step of the approval flow + firstStep, err := approvalFlow.FirstStep() + if err != nil { + return nil, err + } + // Build the steps recursively + processedSteps := make(map[string]*InstanceStep) + err = buildInstanceSteps(ctx, instance, firstStep, nil, data, processedSteps) + if err != nil { + return nil, err } - return instance, nil } +// buildInstanceSteps recursively builds instance steps from approval steps +func buildInstanceSteps(ctx *gin.Context, instance *ApprovalInstance, currentApprovalStep *flow_definition.ApprovalStep, + previousInstanceStep *InstanceStep, data map[string]interface{}, processedSteps map[string]*InstanceStep) error { + // Check if this step has already been processed to avoid cycles + if existingInstanceStep, ok := processedSteps[currentApprovalStep.Key]; ok { + if previousInstanceStep != nil { + connect.InsertNodeAfter(instance, previousInstanceStep, existingInstanceStep) + } + return nil + } + // Create a new instance step + var approverKey string + approver, err := currentApprovalStep.DynamicConfig.GetApprover(ctx, data) + if err != nil { + approverKey = "" + } else { + approverKey = approver.Key + } + currentInstanceStep := NewInstanceStep(currentApprovalStep, approverKey, false) + // Insert the instance step into the instance + if previousInstanceStep == nil { + connect.InsertNodeFirst(instance, currentInstanceStep) + } else { + connect.InsertNodeAfter(instance, previousInstanceStep, currentInstanceStep) + } + // Mark this step as processed + processedSteps[currentApprovalStep.Key] = currentInstanceStep + // Get the next steps and recursively build them + nextSteps, err := connect.GetNextNodes(instance, currentApprovalStep) + if err != nil { + return err + } + for _, nextStep := range nextSteps { + nextApprovalStep := nextStep.(*flow_definition.ApprovalStep) + err := buildInstanceSteps(ctx, instance, nextApprovalStep, currentInstanceStep, data, processedSteps) + if err != nil { + return err + } + } + return nil +} + // SendApprovalNotification 发送审批通知给第一个审批人的方法 func (instance *ApprovalInstance) SendApprovalNotification(step *InstanceStep, ctx context.Context) error { @@ -105,12 +167,7 @@ func (instance *ApprovalInstance) Start(ctx context.Context) error { } // 获取第一个审批步骤 - dataMap, err := instance.DataToMap() - if err != nil { - return err - } - - firstStep, err := instance.getFirstStep(dataMap) + firstStep, err := instance.getFirstStep() if err != nil { return err } @@ -127,39 +184,13 @@ func (instance *ApprovalInstance) Start(ctx context.Context) error { } // getFirstStep 获取第一个审批步骤 -func (instance *ApprovalInstance) getFirstStep(data map[string]interface{}) (*InstanceStep, error) { - // 从模板路径中获取第一个节点 - firstTemplateStep, err := instance.ApprovalFlow.FirstStep(data) - if err != nil { - return nil, fmt.Errorf("error getting first step from template: %v", err) - } - - // 找到实例步骤中对应的第一个模板步骤 - var firstInstanceStep *InstanceStep +func (instance *ApprovalInstance) getFirstStep() (*InstanceStep, error) { for _, step := range instance.Steps { - if step.StepID == firstTemplateStep.ID { - firstInstanceStep = step - break + if step.StepCode == flow_definition.StepStart { + return step, nil } } - - if firstInstanceStep == nil { - return nil, fmt.Errorf("未找到第一个审批步骤") - } - - // 检查动态路径配置,找到优先级更高的路径 - for _, dynamicConfig := range instance.DynamicPathConfigs { - if dynamicConfig.ToStepID == firstTemplateStep.ID { - // 在实例的步骤中找到对应的动态路径起点 - for _, step := range instance.Steps { - if step.StepID == dynamicConfig.FromStepID { - firstInstanceStep = step - break - } - } - } - } - return firstInstanceStep, nil + return nil, errors.New("未找到起点") } // MoveToNextStep 移动到下一个步骤 @@ -205,41 +236,20 @@ func (instance *ApprovalInstance) MoveToNextStep() error { // getNextStep 获取下一个步骤 func (instance *ApprovalInstance) getNextStep(currentStep *InstanceStep, data map[string]interface{}) ([]*InstanceStep, error) { - // 先从动态路径查找下一个步骤 - for _, path := range instance.DynamicPathConfigs { - if path.FromStepID == currentStep.StepID { - stepByID, err := instance.findStepByID(path.ToStepID) - if err != nil { - return nil, err - } - return []*InstanceStep{stepByID}, nil + pathConfigs := currentStep.InstancePathConfigs + var nextSteps []*InstanceStep + for _, pathConfig := range pathConfigs { + met, err := pathConfig.IsConditionMet(data) + if err != nil || !met { + continue } - } - - // 如果动态路径找不到,使用模板路径 , 如果是模板节点,那么必定存在对于的模板路径,如果没有模板路径说明说明审批结束了 - flowStep, err := instance.ApprovalFlow.GetStep(currentStep.StepID) - if err != nil { - return nil, err - } - nextStepIDs, err := flowStep.GetNextStepIDs(data) - if len(nextStepIDs) == 0 { - return nil, fmt.Errorf("未找到符合条件的下一个步骤") - } - - if len(nextStepIDs) == 1 { - // 如果是只有一个模板节点,那么说明是单审批节点 - stepId := nextStepIDs[0] - stepByID, err := instance.findStepByKey(stepId) + nextStep, err := instance.GetStepByKey(pathConfig.GetToNodeKey()) if err != nil { return nil, err } - return []*InstanceStep{stepByID}, nil + nextSteps = append(nextSteps, nextStep) } - - if len(nextStepIDs) > 1 { - // 说明是并行节点 ,暂不支持 - } - return nil, fmt.Errorf("未找到符合条件的下一个步骤") + return nextSteps, nil } func (instance *ApprovalInstance) findStepByID(id int64) (*InstanceStep, error) { @@ -353,9 +363,9 @@ func (instance *ApprovalInstance) GetCurrentStepsById(id int64) *InstanceStep { } // executeApprovalStep 执行审批步骤 -func (instance *ApprovalInstance) executeApprovalStep() error { +func (instance *ApprovalInstance) executeApprovalStep(approvalFlow flow_definition.ApprovalFlow) error { for _, instanceStep := range instance.GetCurrentSteps() { - flowStep, err := instance.ApprovalFlow.GetStep(instanceStep.StepID) + flowStep, err := approvalFlow.GetStep(instanceStep.StepID) if err != nil { continue } @@ -409,24 +419,7 @@ func (instance *ApprovalInstance) executeApprovalStep() error { return nil // 如果没有规则满足,正常返回 } -func (instance *ApprovalInstance) AddStepBetween(step, fromStep, toStep *InstanceStep) *DynamicPathConfig { - // 获取 pathConfig - if fromStep.IsDynamic { - for _, pathConfig := range instance.DynamicPathConfigs { - if pathConfig.FromStepKey == fromStep.Key { - } - } - } else { - steps := instance.ApprovalFlow.Steps - for _, flowStep := range steps { - if flowStep.ID == fromStep.StepID { - flowStep. - } - } - } - -} - -func (instance *ApprovalInstance) GetPathByFromStepKey(fromStepKey string) (*DynamicPathConfig, error) { +func (instance *ApprovalInstance) GetPathByFromStepKey(fromStepKey string) (*InstancePathConfig, error) { + return nil, nil } diff --git a/app/provider/flow_instance/model/dynamic_path_config.go b/app/provider/flow_instance/model/dynamic_path_config.go deleted file mode 100644 index 17509da..0000000 --- a/app/provider/flow_instance/model/dynamic_path_config.go +++ /dev/null @@ -1,28 +0,0 @@ -package model - -import "approveflow/app/base" - -// DynamicPathConfig 动态路径配置表结构 -type DynamicPathConfig struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - InstanceID int64 `gorm:"type:bigint;index;not null" json:"instance_id"` // 关联的审批实例ID - FromStepID int64 `gorm:"type:bigint;not null" json:"from_step_id"` // 来源步骤ID - FromStepKey string `gorm:"type:varchar(50);not null" json:"from_step_key"` // 来源步骤ID - ToStepKey string `gorm:"type:varchar(50);not null" json:"to_step_key"` // 目标步骤ID - IsParallel bool `gorm:"not null;default:false" json:"is_parallel"` // 是否并行 - Priority int `gorm:"not null;default:0" json:"priority"` // 路径优先级 - base.Model -} - -// NewDynamicPathConfig 创建动态路径 -func NewDynamicPathConfig(instanceID, fromStepID int64, FromStepKey, ToStepKey string, isParallel bool) *DynamicPathConfig { - pathConfig := &DynamicPathConfig{ - InstanceID: instanceID, - FromStepID: fromStepID, - FromStepKey: FromStepKey, - ToStepKey: ToStepKey, - IsParallel: isParallel, - Priority: 0, - } - return pathConfig -} diff --git a/app/provider/flow_instance/model/iInstance_step.go b/app/provider/flow_instance/model/iInstance_step.go deleted file mode 100644 index c87012f..0000000 --- a/app/provider/flow_instance/model/iInstance_step.go +++ /dev/null @@ -1,72 +0,0 @@ -package model - -import ( - "approveflow/app/base" - "fmt" -) - -const ( - StepStatusPending = "Pending" // 待审批 - StepStatusApproved = "Approved" // 已批准 - StepStatusRejected = "Rejected" // 已驳回 - StepStatusReversed = "Reversed" // 已反转 - StepStatusCompleted = "Completed" // 步骤已完成 -) - -// InstanceStep 审批步骤实例表 -type InstanceStep struct { - ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID - InstanceID int64 `gorm:"index;not null" json:"instance_id"` // 所属审批实例ID - StepID int64 `gorm:"type:bigint;not null" json:"step_id"` // 关联的流程步骤ID - StepName string `gorm:"type:varchar(50);not null" json:"step_name"` - ApproverKey string `gorm:"type:varchar(50);not null" json:"approver_id"` // 审批人ID - Status string `gorm:"type:varchar(50);not null" json:"status"` // 审批状态 - ApproverComments string `gorm:"type:text" json:"approver_comments"` // 审批意见 - IsDynamic bool `gorm:"not null;default:false" json:"is_dynamic"` // 是否为动态步骤 - Records []*ApprovalRecord `gorm:"foreignKey:InstanceStepID;constraint:OnDelete:CASCADE" json:"records"` // 审批记录 - base.Model -} - -func NewInstanceStep(stepID int64, stepName, approverKey string, isDynamic bool) *InstanceStep { - instanceStep := &InstanceStep{ - StepID: stepID, - StepName: stepName, - ApproverKey: approverKey, - Status: StepStatusPending, - IsDynamic: isDynamic, - } - return instanceStep -} - -// Approve 审批通过 -func (step *InstanceStep) Approve(comments string) error { - if step.Status != StepStatusPending { - return fmt.Errorf("当前步骤不是待审批状态") - } - step.Status = StepStatusApproved - step.ApproverComments = comments - step.addApprovalRecord(StepStatusApproved, comments) - return nil -} - -// Reject 审批驳回 -func (step *InstanceStep) Reject(comments string) error { - if step.Status != StepStatusPending { - return fmt.Errorf("当前步骤不是待审批状态") - } - step.Status = StepStatusRejected - step.ApproverComments = comments - step.addApprovalRecord(StepStatusRejected, comments) - return nil -} - -// addApprovalRecord 添加审批记录 -func (step *InstanceStep) addApprovalRecord(status string, comments string) { - record := &ApprovalRecord{ - InstanceStepID: step.ID, - ApproverKey: step.ApproverKey, - Status: status, - Comments: comments, - } - step.Records = append(step.Records, record) -} diff --git a/app/provider/flow_instance/model/instance_path_config.go b/app/provider/flow_instance/model/instance_path_config.go new file mode 100644 index 0000000..d52a653 --- /dev/null +++ b/app/provider/flow_instance/model/instance_path_config.go @@ -0,0 +1,59 @@ +package model + +import ( + "approveflow/app/base" + "approveflow/app/provider/flow_definition" +) + +// InstancePathConfig 动态路径配置表结构 +type InstancePathConfig struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + NodeID int64 `gorm:"type:bigint;index;not null" json:"node_id"` + FromNodeKey string `gorm:"type:varchar(50);index;not null" json:"from_node_key"` + ToNodeKey string ` gorm:"type:varchar(50);index;not null" json:"to_node_key"` + IsParallel bool `gorm:"not null;default:false" json:"is_parallel"` // 是否并行 + IsDynamic bool `gorm:"not null;default:false" json:"is_dynamic"` // 是否为动态步骤 + Priority int `gorm:"not null;default:0" json:"priority"` // 路径优先级 + flow_definition.ApprovalConditionCommon + base.Model +} + +func (d InstancePathConfig) GetKey() string { + return d.Key +} + +func (d InstancePathConfig) GetNodeID() int64 { + return d.NodeID +} + +func (d InstancePathConfig) GetFromNodeKey() string { + return d.FromNodeKey +} + +func (d InstancePathConfig) GetToNodeKey() string { + return d.ToNodeKey +} + +// NewDynamicPathConfig 创建动态路径 +func NewDynamicPathConfig(instanceID, nodeId int64, FromStepKey, ToStepKey string, isParallel bool) *InstancePathConfig { + pathConfig := &InstancePathConfig{ + NodeID: nodeId, + FromNodeKey: FromStepKey, + ToNodeKey: ToStepKey, + IsParallel: isParallel, + Priority: 0, + } + return pathConfig +} + +// IsConditionMet 判断路径条件是否满足 +func (a *InstancePathConfig) IsConditionMet(data map[string]interface{}) (bool, error) { + if a.ConditionExpression == "" { + return true, nil // 无条件,默认满足 + } + result, err := a.EvaluateCondition(data) + if err != nil { + return false, err + } + return result, nil +} diff --git a/app/provider/flow_instance/model/instance_step.go b/app/provider/flow_instance/model/instance_step.go new file mode 100644 index 0000000..381deb3 --- /dev/null +++ b/app/provider/flow_instance/model/instance_step.go @@ -0,0 +1,101 @@ +package model + +import ( + "approveflow/app/base" + "approveflow/app/provider/abstract/connect" + "approveflow/app/provider/flow_definition" + "approveflow/app/utils" + "fmt" + "github.com/google/uuid" +) + +const ( + StepStatusPending = "Pending" // 待审批 + StepStatusApproved = "Approved" // 已批准 + StepStatusRejected = "Rejected" // 已驳回 + StepStatusReversed = "Reversed" // 已反转 + StepStatusCompleted = "Completed" // 步骤已完成 +) + +// InstanceStep 审批步骤实例表 +type InstanceStep struct { + ID int64 `gorm:"primaryKey;autoIncrement" json:"id"` // 主键ID + InstanceID int64 `gorm:"index;not null" json:"instance_id"` // 所属审批实例ID + StepID int64 `gorm:"type:bigint;not null" json:"step_id"` // 关联的流程步骤ID + StepName string `gorm:"type:varchar(50);not null" json:"step_name"` + StepCode string `gorm:"type:varchar(100)" json:"step_code"` // 步骤编号用于标识特殊的审批节点 + ApproverKey string `gorm:"type:varchar(50);not null" json:"approver_id"` // 审批人ID + Status string `gorm:"type:varchar(50);not null" json:"status"` // 审批状态 + ApproverComments string `gorm:"type:text" json:"approver_comments"` // 审批意见 + IsDynamic bool `gorm:"not null;default:false" json:"is_dynamic"` // 是否为动态步骤 + InstancePathConfigs []*InstancePathConfig `gorm:"foreignKey:NodeID;constraint:OnDelete:CASCADE" json:"instance_path_configs"` // 动态路径配置,自定义,不直接存储 + Records []*ApprovalRecord `gorm:"foreignKey:InstanceStepID;constraint:OnDelete:CASCADE" json:"records"` // 审批记录 + base.Model +} + +func (step *InstanceStep) GetPathConfigs() []connect.AbstractNodePathConfig { + return utils.ConvertToAbstractNodes(step.InstancePathConfigs, func(t *InstancePathConfig) connect.AbstractNodePathConfig { + return t + }) +} + +func (step *InstanceStep) SetPathConfigs(configs []connect.AbstractNodePathConfig) { + convertedConfigs, err := utils.ConvertToSpecificType(configs, func(item connect.AbstractNodePathConfig) (*InstancePathConfig, bool) { + specificConfig, ok := item.(*InstancePathConfig) + return specificConfig, ok + }) + if err != nil { + panic(fmt.Sprintf("Error in SetPathConfigs: %v", err)) + } + step.InstancePathConfigs = convertedConfigs +} + +func (step *InstanceStep) GetKey() string { + return step.Key +} + +func NewInstanceStep(flowStep *flow_definition.ApprovalStep, approverKey string, isDynamic bool) *InstanceStep { + instanceStep := &InstanceStep{ + StepID: flowStep.ID, + StepName: flowStep.Name, + StepCode: flowStep.StepCode, + ApproverKey: approverKey, + Status: StepStatusPending, + IsDynamic: isDynamic, + } + instanceStep.Key = uuid.New().String() + return instanceStep +} + +// Approve 审批通过 +func (step *InstanceStep) Approve(comments string) error { + if step.Status != StepStatusPending { + return fmt.Errorf("当前步骤不是待审批状态") + } + step.Status = StepStatusApproved + step.ApproverComments = comments + step.addApprovalRecord(StepStatusApproved, comments) + return nil +} + +// Reject 审批驳回 +func (step *InstanceStep) Reject(comments string) error { + if step.Status != StepStatusPending { + return fmt.Errorf("当前步骤不是待审批状态") + } + step.Status = StepStatusRejected + step.ApproverComments = comments + step.addApprovalRecord(StepStatusRejected, comments) + return nil +} + +// addApprovalRecord 添加审批记录 +func (step *InstanceStep) addApprovalRecord(status string, comments string) { + record := &ApprovalRecord{ + InstanceStepID: step.ID, + ApproverKey: step.ApproverKey, + Status: status, + Comments: comments, + } + step.Records = append(step.Records, record) +} diff --git a/app/provider/flow_instance/service.go b/app/provider/flow_instance/service.go index 8353efb..f71c618 100644 --- a/app/provider/flow_instance/service.go +++ b/app/provider/flow_instance/service.go @@ -82,9 +82,10 @@ func (f *FlowInstanceService) AddCustomStep(ctx context.Context, instanceID int6 } // 如果是 自定义阶段 那么它的路径配置能够实例对象中找到 if fromStep.IsDynamic { - instance.DynamicPathConfigs + //instance.InstancePathConfigs } + return nil }) } diff --git a/app/utils/convert.go b/app/utils/convert.go index f07f8a4..6870c47 100644 --- a/app/utils/convert.go +++ b/app/utils/convert.go @@ -3,10 +3,31 @@ package utils import ( "approveflow/app/http/base" "encoding/json" + "fmt" "github.com/Superdanda/hade/framework/gin" "net/http" ) +func ConvertToSpecificType[T any, I interface{}](items []I, convertFunc func(I) (T, bool)) ([]T, error) { + specificItems := make([]T, 0, len(items)) + for i, item := range items { + if specificItem, ok := convertFunc(item); ok { + specificItems = append(specificItems, specificItem) + } else { + return nil, fmt.Errorf("invalid type at index %d", i) + } + } + return specificItems, nil +} + +func ConvertToAbstractNodes[T any, I interface{}](items []T, toInterface func(T) I) []I { + nodes := make([]I, len(items)) + for i, item := range items { + nodes[i] = toInterface(item) + } + return nodes +} + func QuickBind[T any](c *gin.Context) *T { var params T if err := c.ShouldBindJSON(¶ms); err != nil {