From 1dfc2f9245273ee5cdf64f1eff1dc345944723d4 Mon Sep 17 00:00:00 2001 From: lulz1 Date: Tue, 19 Nov 2024 17:03:12 +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 --- app/base/approval_constant.go | 3 + app/http/module/flow_instance/api.go | 5 +- .../flow_instance/api_instances_create.go | 9 + .../flow_instance/api_instances_start_rule.go | 34 +++ .../api_instances_steps_approve.go | 36 +++ .../api_instances_steps_cancel.go | 34 +++ .../api_instances_steps_reject.go | 36 +++ app/http/module/flow_instance/dto.go | 16 +- app/http/module/flow_instance/mapper.go | 7 + app/http/swagger/docs.go | 220 ++++++++++++++ app/http/swagger/swagger.json | 220 ++++++++++++++ app/http/swagger/swagger.yaml | 144 +++++++++ app/infrastructure/Instance_repository.go | 9 +- app/infrastructure/flow_repository.go | 2 +- app/provider/abstract/connect/node.go | 9 +- .../flow_definition/approval_condition.go | 2 +- .../flow_definition/model_approval_dynamic.go | 9 + .../flow_definition/model_approval_flow.go | 1 + .../flow_definition/model_approval_step.go | 3 + app/provider/flow_definition/service.go | 12 +- app/provider/flow_instance/contract.go | 2 + .../flow_instance/model/approval_instance.go | 274 +++++++++++++++--- .../flow_instance/model/approval_record.go | 9 + .../flow_instance/model/approval_reversal.go | 52 +++- .../model/batch_approval_task.go | 15 + .../model/instance_path_config.go | 23 +- .../flow_instance/model/instance_step.go | 104 +++++-- .../flow_instance/model/model_test.go | 24 ++ app/provider/flow_instance/service.go | 35 ++- app/provider/user/service.go | 20 ++ 30 files changed, 1266 insertions(+), 103 deletions(-) create mode 100644 app/http/module/flow_instance/api_instances_start_rule.go create mode 100644 app/http/module/flow_instance/api_instances_steps_approve.go create mode 100644 app/http/module/flow_instance/api_instances_steps_cancel.go create mode 100644 app/http/module/flow_instance/api_instances_steps_reject.go create mode 100644 app/provider/flow_instance/model/model_test.go diff --git a/app/base/approval_constant.go b/app/base/approval_constant.go index 9605602..734b47c 100644 --- a/app/base/approval_constant.go +++ b/app/base/approval_constant.go @@ -8,4 +8,7 @@ const ( CreatorKey = "CreatorKey" ApproverKey = "ApproverKey" ApplicantKey = "ApplicantKey" + + TenantKey = "TenantKey" + AssociateKey = "AssociateKey" ) diff --git a/app/http/module/flow_instance/api.go b/app/http/module/flow_instance/api.go index 27edb36..adabde3 100644 --- a/app/http/module/flow_instance/api.go +++ b/app/http/module/flow_instance/api.go @@ -17,9 +17,13 @@ func RegisterRoutes(r *gin.Engine) error { { instancesGroup.POST("/create", api.InstancesCreate) instancesGroup.POST("/start", api.InstancesStart) + instancesGroup.POST("/start/rule", api.InstancesStartRule) stepsGroup := instancesGroup.Group("/steps") { stepsGroup.POST("/add", api.InstancesStepsAdd) + stepsGroup.POST("/approve", api.InstancesStepsApprove) + stepsGroup.POST("/reject", api.InstancesStepsReject) + stepsGroup.POST("/cancel", api.InstancesStepsCancel) stepsGroup.POST("/update", api.InstancesStepsUpdate) stepsGroup.POST("/revert", api.InstancesStepsRevert) } @@ -30,6 +34,5 @@ func RegisterRoutes(r *gin.Engine) error { } } } - return nil } diff --git a/app/http/module/flow_instance/api_instances_create.go b/app/http/module/flow_instance/api_instances_create.go index 3861b82..587f9c9 100644 --- a/app/http/module/flow_instance/api_instances_create.go +++ b/app/http/module/flow_instance/api_instances_create.go @@ -13,6 +13,8 @@ type InstancesCreateParam struct { FlowID int64 `json:"flow_id"` ApplicantKey string `json:"applicant_key"` //申请人ID CreatorKey string `json:"creator_key"` + AssociateKey string `json:"associate_key"` + TenantKey string `json:"tenant_key"` } // InstancesCreate handler @@ -33,6 +35,13 @@ func (api *FlowInstanceApi) InstancesCreate(c *gin.Context) { data := make(map[string]interface{}) data[base.ApplicantKey] = param.ApplicantKey data[base.CreatorKey] = param.CreatorKey + + if param.AssociateKey != "" { + data[base.AssociateKey] = param.AssociateKey + } + if param.TenantKey != "" { + data[base.TenantKey] = param.TenantKey + } flow, err := definitionService.GetFlow(c, param.FlowID) if err != nil { res.FailWithErr(c, err) diff --git a/app/http/module/flow_instance/api_instances_start_rule.go b/app/http/module/flow_instance/api_instances_start_rule.go new file mode 100644 index 0000000..fcb5df8 --- /dev/null +++ b/app/http/module/flow_instance/api_instances_start_rule.go @@ -0,0 +1,34 @@ +package flow_instance + +import ( + "approveflow/app/http/base/res" + "approveflow/app/provider/flow_instance" + "approveflow/app/utils" + "github.com/Superdanda/hade/framework/gin" +) + +type InstancesStartRuleParam struct { + InstanceID int64 `json:"instance_id"` +} + +// InstancesStartRule handler +// @Summary 启动审批流自动规则 +// @Description 启动审批流自动规则 +// @ID instances-start-rule +// @Tags flow-instances +// @Accept json +// @Produce json +// @Param InstancesStartRuleParam body InstancesStartRuleParam true "启动实例ID" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" +// @Router /instances/start/rule [post] +func (api *FlowInstanceApi) InstancesStartRule(context *gin.Context) { + param := utils.QuickBind[InstancesStartParam](context) + instanceService := context.MustMake(flow_instance.FlowInstanceKey).(flow_instance.Service) + _, err := instanceService.StartInstanceRule(context, param.InstanceID) + if err != nil { + res.FailWithErr(context, err) + return + } + res.Success(context) +} diff --git a/app/http/module/flow_instance/api_instances_steps_approve.go b/app/http/module/flow_instance/api_instances_steps_approve.go new file mode 100644 index 0000000..2b72b71 --- /dev/null +++ b/app/http/module/flow_instance/api_instances_steps_approve.go @@ -0,0 +1,36 @@ +package flow_instance + +import ( + "approveflow/app/http/base/res" + "approveflow/app/provider/flow_instance" + "approveflow/app/utils" + "github.com/Superdanda/hade/framework/gin" +) + +type InstancesStepsApproveParam struct { + InstanceID int64 `json:"instance_id"` + StepID int64 `json:"step_id"` + Comments string `json:"comments"` +} + +// InstancesStepsApprove handler +// @Summary 审批通过 +// @Description 根据实例号和节点号通过审批 +// @ID instances-steps-approve +// @Tags flow-instances +// @Accept json +// @Produce json +// @Param InstancesStepsApproveParam body InstancesStepsApproveParam true "输入参数描述" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" +// @Router /instances/steps/approve [post] +func (api *FlowInstanceApi) InstancesStepsApprove(context *gin.Context) { + param := utils.QuickBind[InstancesStepsApproveParam](context) + instanceService := context.MustMake(flow_instance.FlowInstanceKey).(flow_instance.Service) + err := instanceService.ApproveStep(context, param.InstanceID, param.StepID, param.Comments) + if err != nil { + res.FailWithErr(context, err) + return + } + res.Success(context) +} diff --git a/app/http/module/flow_instance/api_instances_steps_cancel.go b/app/http/module/flow_instance/api_instances_steps_cancel.go new file mode 100644 index 0000000..df0e540 --- /dev/null +++ b/app/http/module/flow_instance/api_instances_steps_cancel.go @@ -0,0 +1,34 @@ +package flow_instance + +import ( + "approveflow/app/http/base/res" + "approveflow/app/provider/flow_instance" + "approveflow/app/utils" + "github.com/Superdanda/hade/framework/gin" +) + +type InstancesStepsCancelParam struct { + InstanceID int64 `json:"instance_id"` +} + +// InstancesStepsCancel handler +// @Summary 审批取消 +// @Description 根据实例号取消审批 +// @ID instances-steps-cancel +// @Tags flow-instances +// @Accept json +// @Produce json +// @Param InstancesStepsCancelParam body InstancesStepsCancelParam true "审批取消参数" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" +// @Router /instances/steps/cancel [post] +func (api *FlowInstanceApi) InstancesStepsCancel(context *gin.Context) { + param := utils.QuickBind[InstancesStepsCancelParam](context) + instanceService := context.MustMake(flow_instance.FlowInstanceKey).(flow_instance.Service) + err := instanceService.CancelInstance(context, param.InstanceID) + if err != nil { + res.FailWithErr(context, err) + return + } + res.Success(context) +} diff --git a/app/http/module/flow_instance/api_instances_steps_reject.go b/app/http/module/flow_instance/api_instances_steps_reject.go new file mode 100644 index 0000000..6f01770 --- /dev/null +++ b/app/http/module/flow_instance/api_instances_steps_reject.go @@ -0,0 +1,36 @@ +package flow_instance + +import ( + "approveflow/app/http/base/res" + "approveflow/app/provider/flow_instance" + "approveflow/app/utils" + "github.com/Superdanda/hade/framework/gin" +) + +type InstancesStepsRejectParam struct { + InstanceID int64 `json:"instance_id"` + StepID int64 `json:"step_id"` + Comments string `json:"comments"` +} + +// InstancesStepsReject handler +// @Summary 审批驳回 +// @Description 根据实例号和节点号驳回审批 +// @ID instances-steps-reject +// @Tags flow-instances +// @Accept json +// @Produce json +// @Param InstancesStepsRejectParam body InstancesStepsRejectParam true "驳回参数" +// @Success 200 {object} base.Result "返回成功的流程定义数据" +// @Failure 500 {object} base.Result "返回失败的流程定义数据" +// @Router /instances/steps/reject [post] +func (api *FlowInstanceApi) InstancesStepsReject(context *gin.Context) { + param := utils.QuickBind[InstancesStepsRejectParam](context) + instanceService := context.MustMake(flow_instance.FlowInstanceKey).(flow_instance.Service) + err := instanceService.RejectStep(context, param.InstanceID, param.StepID, param.Comments) + if err != nil { + res.FailWithErr(context, err) + return + } + res.Success(context) +} diff --git a/app/http/module/flow_instance/dto.go b/app/http/module/flow_instance/dto.go index 384a508..33c1be9 100644 --- a/app/http/module/flow_instance/dto.go +++ b/app/http/module/flow_instance/dto.go @@ -9,6 +9,7 @@ type ApprovalInstanceDTO struct { CurrentStepIDs []*CurrentStepDTO `json:"current_step_ids"` // 当前步骤ID Steps []*InstanceStepDTO `json:"steps"` // 实例步骤 DynamicPathConfigs []*DynamicPathConfigDTO `json:"dynamic_path_configs"` // 动态路径配置,自定义,不直接存储 + Key string `json:"key"` } type CurrentStepDTO struct { @@ -25,6 +26,7 @@ type InstanceStepDTO struct { ApproverComments string `json:"approver_comments"` // 审批意见 IsDynamic bool `json:"is_dynamic"` // 是否为动态步骤 Records []*ApprovalRecordDTO `json:"records"` // 审批记录 + Key string `json:"key"` } type ApprovalRecordDTO struct { @@ -34,13 +36,15 @@ type ApprovalRecordDTO struct { Status string `json:"status"` // 审批状态 Comments string `json:"comments"` // 审批意见 IsTimeout bool `json:"is_timeout"` // 是否超时 + Key string `json:"key"` } type DynamicPathConfigDTO struct { - ID int64 `json:"id"` // 主键ID - InstanceID int64 `json:"instance_id"` // 关联的审批实例ID - FromStepID int64 `json:"from_step_id"` // 来源步骤ID - ToStepID int64 `json:"to_step_id"` // 目标步骤ID - IsParallel bool `json:"is_parallel"` // 是否并行 - Priority int `json:"priority"` // 路径优先级 + ID int64 `json:"id"` // 主键ID + InstanceID int64 `json:"instance_id"` // 关联的审批实例ID + FromStepID int64 `json:"from_step_id"` // 来源步骤ID + ToStepID int64 `json:"to_step_id"` // 目标步骤ID + IsParallel bool `json:"is_parallel"` // 是否并行 + Priority int `json:"priority"` // 路径优先级 + Key string `json:"key"` } diff --git a/app/http/module/flow_instance/mapper.go b/app/http/module/flow_instance/mapper.go index 67ca44e..ca62e43 100644 --- a/app/http/module/flow_instance/mapper.go +++ b/app/http/module/flow_instance/mapper.go @@ -8,6 +8,9 @@ import ( func MapApprovalInstanceToDTOBatch(approvalInstances []*instanceModel.ApprovalInstance) ([]*ApprovalInstanceDTO, error) { res := make([]*ApprovalInstanceDTO, 0, len(approvalInstances)) for _, instance := range approvalInstances { + for _, step := range instance.Steps { + step.Status = step.GetStatus() + } dto, err := MapApprovalInstanceToDTO(instance) if err != nil { return nil, err @@ -21,6 +24,10 @@ func MapApprovalInstanceToDTO(approvalInstance *instanceModel.ApprovalInstance) // 创建目标 ApprovalInstanceDTO 对象 approvalInstanceDTO := &ApprovalInstanceDTO{} + for _, step := range approvalInstance.Steps { + step.Status = step.GetStatus() + } + // 使用 Convert 方法将 实体对象 转换为 DTO if err := utils.Convert(approvalInstance, approvalInstanceDTO); err != nil { return nil, err diff --git a/app/http/swagger/docs.go b/app/http/swagger/docs.go index 5f3faca..13faaad 100644 --- a/app/http/swagger/docs.go +++ b/app/http/swagger/docs.go @@ -408,6 +408,47 @@ const docTemplate = `{ } } }, + "/instances/start/rule": { + "post": { + "description": "启动审批流自动规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "启动审批流自动规则", + "operationId": "instances-start-rule", + "parameters": [ + { + "description": "启动实例ID", + "name": "InstancesStartRuleParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStartRuleParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, "/instances/steps/add": { "post": { "description": "输入你的接口总结详情", @@ -449,6 +490,129 @@ const docTemplate = `{ } } }, + "/instances/steps/approve": { + "post": { + "description": "根据实例号和节点号通过审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批通过", + "operationId": "instances-steps-approve", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsApproveParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsApproveParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/cancel": { + "post": { + "description": "根据实例号取消审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批取消", + "operationId": "instances-steps-cancel", + "parameters": [ + { + "description": "审批取消参数", + "name": "InstancesStepsCancelParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsCancelParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/reject": { + "post": { + "description": "根据实例号和节点号驳回审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批驳回", + "operationId": "instances-steps-reject", + "parameters": [ + { + "description": "驳回参数", + "name": "InstancesStepsRejectParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsRejectParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, "/instances/steps/revert": { "post": { "description": "输入你的接口总结详情", @@ -673,6 +837,9 @@ const docTemplate = `{ "description": "主键ID", "type": "integer" }, + "key": { + "type": "string" + }, "status": { "description": "审批状态", "type": "string" @@ -709,6 +876,9 @@ const docTemplate = `{ "description": "是否超时", "type": "boolean" }, + "key": { + "type": "string" + }, "status": { "description": "审批状态", "type": "string" @@ -745,6 +915,9 @@ const docTemplate = `{ "description": "是否并行", "type": "boolean" }, + "key": { + "type": "string" + }, "priority": { "description": "路径优先级", "type": "integer" @@ -778,6 +951,9 @@ const docTemplate = `{ "description": "是否为动态步骤", "type": "boolean" }, + "key": { + "type": "string" + }, "records": { "description": "审批记录", "type": "array", @@ -839,9 +1015,53 @@ const docTemplate = `{ } } }, + "flow_instance.InstancesStartRuleParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, "flow_instance.InstancesStepsAddParam": { "type": "object" }, + "flow_instance.InstancesStepsApproveParam": { + "type": "object", + "properties": { + "comments": { + "type": "string" + }, + "instance_id": { + "type": "integer" + }, + "step_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsCancelParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsRejectParam": { + "type": "object", + "properties": { + "comments": { + "type": "string" + }, + "instance_id": { + "type": "integer" + }, + "step_id": { + "type": "integer" + } + } + }, "flow_instance.InstancesStepsRevertParam": { "type": "object" }, diff --git a/app/http/swagger/swagger.json b/app/http/swagger/swagger.json index af7cdcf..7288a74 100644 --- a/app/http/swagger/swagger.json +++ b/app/http/swagger/swagger.json @@ -401,6 +401,47 @@ } } }, + "/instances/start/rule": { + "post": { + "description": "启动审批流自动规则", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "启动审批流自动规则", + "operationId": "instances-start-rule", + "parameters": [ + { + "description": "启动实例ID", + "name": "InstancesStartRuleParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStartRuleParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, "/instances/steps/add": { "post": { "description": "输入你的接口总结详情", @@ -442,6 +483,129 @@ } } }, + "/instances/steps/approve": { + "post": { + "description": "根据实例号和节点号通过审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批通过", + "operationId": "instances-steps-approve", + "parameters": [ + { + "description": "输入参数描述", + "name": "InstancesStepsApproveParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsApproveParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/cancel": { + "post": { + "description": "根据实例号取消审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批取消", + "operationId": "instances-steps-cancel", + "parameters": [ + { + "description": "审批取消参数", + "name": "InstancesStepsCancelParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsCancelParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, + "/instances/steps/reject": { + "post": { + "description": "根据实例号和节点号驳回审批", + "consumes": [ + "application/json" + ], + "produces": [ + "application/json" + ], + "tags": [ + "flow-instances" + ], + "summary": "审批驳回", + "operationId": "instances-steps-reject", + "parameters": [ + { + "description": "驳回参数", + "name": "InstancesStepsRejectParam", + "in": "body", + "required": true, + "schema": { + "$ref": "#/definitions/flow_instance.InstancesStepsRejectParam" + } + } + ], + "responses": { + "200": { + "description": "返回成功的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + }, + "500": { + "description": "返回失败的流程定义数据", + "schema": { + "$ref": "#/definitions/base.Result" + } + } + } + } + }, "/instances/steps/revert": { "post": { "description": "输入你的接口总结详情", @@ -666,6 +830,9 @@ "description": "主键ID", "type": "integer" }, + "key": { + "type": "string" + }, "status": { "description": "审批状态", "type": "string" @@ -702,6 +869,9 @@ "description": "是否超时", "type": "boolean" }, + "key": { + "type": "string" + }, "status": { "description": "审批状态", "type": "string" @@ -738,6 +908,9 @@ "description": "是否并行", "type": "boolean" }, + "key": { + "type": "string" + }, "priority": { "description": "路径优先级", "type": "integer" @@ -771,6 +944,9 @@ "description": "是否为动态步骤", "type": "boolean" }, + "key": { + "type": "string" + }, "records": { "description": "审批记录", "type": "array", @@ -832,9 +1008,53 @@ } } }, + "flow_instance.InstancesStartRuleParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, "flow_instance.InstancesStepsAddParam": { "type": "object" }, + "flow_instance.InstancesStepsApproveParam": { + "type": "object", + "properties": { + "comments": { + "type": "string" + }, + "instance_id": { + "type": "integer" + }, + "step_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsCancelParam": { + "type": "object", + "properties": { + "instance_id": { + "type": "integer" + } + } + }, + "flow_instance.InstancesStepsRejectParam": { + "type": "object", + "properties": { + "comments": { + "type": "string" + }, + "instance_id": { + "type": "integer" + }, + "step_id": { + "type": "integer" + } + } + }, "flow_instance.InstancesStepsRevertParam": { "type": "object" }, diff --git a/app/http/swagger/swagger.yaml b/app/http/swagger/swagger.yaml index aed8790..5c17481 100644 --- a/app/http/swagger/swagger.yaml +++ b/app/http/swagger/swagger.yaml @@ -97,6 +97,8 @@ definitions: id: description: 主键ID type: integer + key: + type: string status: description: 审批状态 type: string @@ -123,6 +125,8 @@ definitions: is_timeout: description: 是否超时 type: boolean + key: + type: string status: description: 审批状态 type: string @@ -148,6 +152,8 @@ definitions: is_parallel: description: 是否并行 type: boolean + key: + type: string priority: description: 路径优先级 type: integer @@ -172,6 +178,8 @@ definitions: is_dynamic: description: 是否为动态步骤 type: boolean + key: + type: string records: description: 审批记录 items: @@ -213,8 +221,36 @@ definitions: instance_id: type: integer type: object + flow_instance.InstancesStartRuleParam: + properties: + instance_id: + type: integer + type: object flow_instance.InstancesStepsAddParam: type: object + flow_instance.InstancesStepsApproveParam: + properties: + comments: + type: string + instance_id: + type: integer + step_id: + type: integer + type: object + flow_instance.InstancesStepsCancelParam: + properties: + instance_id: + type: integer + type: object + flow_instance.InstancesStepsRejectParam: + properties: + comments: + type: string + instance_id: + type: integer + step_id: + type: integer + type: object flow_instance.InstancesStepsRevertParam: type: object flow_instance.InstancesStepsUpdateParam: @@ -473,6 +509,33 @@ paths: summary: 启动审批流 tags: - flow-instances + /instances/start/rule: + post: + consumes: + - application/json + description: 启动审批流自动规则 + operationId: instances-start-rule + parameters: + - description: 启动实例ID + in: body + name: InstancesStartRuleParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStartRuleParam' + 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: @@ -500,6 +563,87 @@ paths: summary: 输入你的接口总结 tags: - flow-instances + /instances/steps/approve: + post: + consumes: + - application/json + description: 根据实例号和节点号通过审批 + operationId: instances-steps-approve + parameters: + - description: 输入参数描述 + in: body + name: InstancesStepsApproveParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsApproveParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 审批通过 + tags: + - flow-instances + /instances/steps/cancel: + post: + consumes: + - application/json + description: 根据实例号取消审批 + operationId: instances-steps-cancel + parameters: + - description: 审批取消参数 + in: body + name: InstancesStepsCancelParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsCancelParam' + produces: + - application/json + responses: + "200": + description: 返回成功的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + "500": + description: 返回失败的流程定义数据 + schema: + $ref: '#/definitions/base.Result' + summary: 审批取消 + tags: + - flow-instances + /instances/steps/reject: + post: + consumes: + - application/json + description: 根据实例号和节点号驳回审批 + operationId: instances-steps-reject + parameters: + - description: 驳回参数 + in: body + name: InstancesStepsRejectParam + required: true + schema: + $ref: '#/definitions/flow_instance.InstancesStepsRejectParam' + 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: diff --git a/app/infrastructure/Instance_repository.go b/app/infrastructure/Instance_repository.go index 8b2acbf..9eab6c4 100644 --- a/app/infrastructure/Instance_repository.go +++ b/app/infrastructure/Instance_repository.go @@ -38,18 +38,23 @@ func NewOrmInstanceRepositoryImplAndRegister(container framework.Container) { } func (u *InstanceRepositoryImpl) SaveToDB(entity *InstanceModel.ApprovalInstance) error { + // 先保存 ApprovalFlow 本身 + if entity.ID != 0 { + // 使用FullSaveAssociations 方法来确保模型的整体状态,包括其所有关联都反映在了数据库中,从在应用中保持数据的完整性和一致性 + return u.db.Session(&gorm.Session{FullSaveAssociations: true}).Updates(entity).Error + } return u.db.Save(entity).Error } func (u *InstanceRepositoryImpl) FindByIDFromDB(id int64) (*InstanceModel.ApprovalInstance, error) { entity := &InstanceModel.ApprovalInstance{} - err := u.db.First(entity, id).Error + err := u.db.Preload("CurrentStepIDs").Preload("StatusEvents").Preload("Steps").Preload("Steps.StatusEvents").Preload("Steps.Records").Preload("Steps.InstancePathConfigs").First(entity, id).Error return entity, err } func (u *InstanceRepositoryImpl) FindByIDsFromDB(ids []int64) ([]*InstanceModel.ApprovalInstance, error) { var entities []*InstanceModel.ApprovalInstance - err := u.db.Where("id IN ?", ids).Find(&entities).Error + err := u.db.Where("id IN ?", ids).Preload("StatusEvents").Preload("CurrentStepIDs").Preload("Steps").Preload("Steps.StatusEvents").Preload("Steps.Records").Preload("Steps.InstancePathConfigs").Find(&entities).Error return entities, err } diff --git a/app/infrastructure/flow_repository.go b/app/infrastructure/flow_repository.go index 4dd032d..3889c4d 100644 --- a/app/infrastructure/flow_repository.go +++ b/app/infrastructure/flow_repository.go @@ -53,7 +53,7 @@ func (u *FlowRepositoryImpl) FindByIDFromDB(id int64) (*FlowDefinitionModule.App func (u *FlowRepositoryImpl) FindByIDsFromDB(ids []int64) ([]*FlowDefinitionModule.ApprovalFlow, error) { var entities []*FlowDefinitionModule.ApprovalFlow - err := u.db.Where("id IN ?", ids).Find(&entities).Error + err := u.db.Where("id IN ?", ids).Preload("Steps").Preload("Steps.Rules").Preload("Steps.DynamicConfig").Preload("Steps.PathConfigs").Find(&entities).Error return entities, err } diff --git a/app/provider/abstract/connect/node.go b/app/provider/abstract/connect/node.go index 94ba7b5..d70d9c8 100644 --- a/app/provider/abstract/connect/node.go +++ b/app/provider/abstract/connect/node.go @@ -16,6 +16,7 @@ type NodeManager interface { SetNodes([]AbstractNode) GetNodeMap() map[string]AbstractNode NewNodePathConfig(fromNodeKey, toNodeKey string) AbstractNodePathConfig + Delete() } type NodeManagerS struct { @@ -26,6 +27,7 @@ type AbstractNode interface { GetPathConfigs() []AbstractNodePathConfig SetPathConfigs([]AbstractNodePathConfig) GetKey() string + Delete() } type AbstractNodePathConfig interface { @@ -33,6 +35,7 @@ type AbstractNodePathConfig interface { GetNodeID() int64 GetFromNodeKey() string GetToNodeKey() string + Delete() } type Node struct { @@ -270,9 +273,11 @@ func addPath(nct NodeManager, fromNode, toNode AbstractNode) { 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) + if pc.GetFromNodeKey() == fromNode.GetKey() && pc.GetToNodeKey() == toNode.GetKey() { + // 使用软删除的方式 + pc.Delete() } + newPathConfigs = append(newPathConfigs, pc) } fromNode.SetPathConfigs(newPathConfigs) } diff --git a/app/provider/flow_definition/approval_condition.go b/app/provider/flow_definition/approval_condition.go index b7a0523..cb67787 100644 --- a/app/provider/flow_definition/approval_condition.go +++ b/app/provider/flow_definition/approval_condition.go @@ -11,7 +11,7 @@ type ApprovalCondition interface { } type ApprovalConditionCommon struct { - ApprovalCondition + ApprovalCondition `gorm:"-"` ConditionExpression string `gorm:"type:text" json:"condition_expression"` // 条件表达式 } diff --git a/app/provider/flow_definition/model_approval_dynamic.go b/app/provider/flow_definition/model_approval_dynamic.go index 5256e93..1c879bb 100644 --- a/app/provider/flow_definition/model_approval_dynamic.go +++ b/app/provider/flow_definition/model_approval_dynamic.go @@ -3,6 +3,7 @@ package flow_definition import ( "approveflow/app/base" userProvider "approveflow/app/provider/user" + "encoding/json" "github.com/Superdanda/hade/framework/gin" ) @@ -22,6 +23,14 @@ type DynamicApprovalStepConfig struct { base.Model // 通用字段,包括创建时间、更新时间等 } +func (config *DynamicApprovalStepConfig) MarshalBinary() ([]byte, error) { + return json.Marshal(config) +} + +func (config *DynamicApprovalStepConfig) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, config) +} + // NewDirectSupervisorRule 创建审批人为直属领导审批规则 func NewDirectSupervisorRule() *DynamicApprovalStepConfig { directSupervisorApproval := &DynamicApprovalStepConfig{ diff --git a/app/provider/flow_definition/model_approval_flow.go b/app/provider/flow_definition/model_approval_flow.go index 1520cf4..63e6e34 100644 --- a/app/provider/flow_definition/model_approval_flow.go +++ b/app/provider/flow_definition/model_approval_flow.go @@ -15,6 +15,7 @@ type ApprovalFlow struct { 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"` // 流程步骤 + TenantKey string `gorm:"type:varchar(50);not null" json:"tenant_key"` base.Model // 通用字段,包括创建时间、更新时间等 } diff --git a/app/provider/flow_definition/model_approval_step.go b/app/provider/flow_definition/model_approval_step.go index 0824c14..a494a11 100644 --- a/app/provider/flow_definition/model_approval_step.go +++ b/app/provider/flow_definition/model_approval_step.go @@ -38,6 +38,9 @@ func (step *ApprovalStep) SetPathConfigs(configs []connect.AbstractNodePathConfi if err != nil { panic(fmt.Sprintf("Error in SetPathConfigs: %v", err)) } + for _, path := range step.PathConfigs { + path.Delete() + } step.PathConfigs = convertedConfigs } diff --git a/app/provider/flow_definition/service.go b/app/provider/flow_definition/service.go index cfde215..04738e3 100644 --- a/app/provider/flow_definition/service.go +++ b/app/provider/flow_definition/service.go @@ -1,6 +1,7 @@ package flow_definition import ( + "approveflow/app/provider/abstract/connect" "context" "fmt" "github.com/Superdanda/hade/framework" @@ -40,18 +41,11 @@ func (f *FlowDefinitionService) DeleteFlow(ctx context.Context, flowID int64) er func (f *FlowDefinitionService) AddStepWithPosition(ctx context.Context, flowID int64, step *ApprovalStep, fromStepId string, toStepKey string) error { return f.handlerFlow(ctx, flowID, func(ctx context.Context, flow *ApprovalFlow) error { fromStep, err := flow.GetStepByKey(fromStepId) - _, err = flow.GetStepByKey(toStepKey) - + toStep, err := flow.GetStepByKey(toStepKey) if err != nil { return err } - path, ok := fromStep.GetPathByToKey(toStepKey) - if !ok { - return errors.New("这两个节点不相邻") - } - _, toPath := path.NewPathWithStep(step) - err = step.AddPathConfig(toPath) - err = flow.AddStep(step) + err = connect.InsertNodeBetween(flow, fromStep, toStep, step) return err }) } diff --git a/app/provider/flow_instance/contract.go b/app/provider/flow_instance/contract.go index 498fc5a..7a70442 100644 --- a/app/provider/flow_instance/contract.go +++ b/app/provider/flow_instance/contract.go @@ -14,6 +14,8 @@ type Service interface { CreateInstance(ctx *gin.Context, approvalFlow *flow_definition.ApprovalFlow, data map[string]interface{}) (*model.ApprovalInstance, error) // StartInstance 功能:启动新的审批流程实例。 StartInstance(ctx context.Context, instanceID int64) (*model.ApprovalInstance, error) + // StartInstanceRule 功能:启动新的审批流程实例规则。 + StartInstanceRule(ctx context.Context, instanceID int64) (*model.ApprovalInstance, error) // GetInstance 功能:获取指定流程实例的详细信息。 GetInstance(ctx context.Context, instanceID int64) (*model.ApprovalInstance, error) // GetInstancePage 功能:分页查询流程实例的详细信息。 diff --git a/app/provider/flow_instance/model/approval_instance.go b/app/provider/flow_instance/model/approval_instance.go index b84fd06..5f5f14c 100644 --- a/app/provider/flow_instance/model/approval_instance.go +++ b/app/provider/flow_instance/model/approval_instance.go @@ -15,6 +15,7 @@ import ( const ( StatusCreated = "Created" // 审批实例已创建 + StatusReject = "Reject" // 审批实例已创建 StatusInProgress = "InProgress" // 审批进行中 StatusCompleted = "Completed" // 审批完成 StatusCancelled = "Cancelled" // 审批取消 @@ -22,18 +23,51 @@ 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"` // 实例步骤 - Data string `gorm:"type:json" json:"data"` // 保存审批数据的 JSON + 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:"-" 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"` // 实例步骤 + StatusEvents []*InstanceStatusEvent `gorm:"foreignKey:InstanceID;constraint:OnDelete:CASCADE" json:"status_events"` // 实例步骤 + Data string `gorm:"type:json" json:"data"` + NodeMap map[string]connect.AbstractNode `gorm:"-" json:"node_map"` // 保存审批数据的 JSON + TenantKey string `gorm:"type:varchar(50);not null" json:"tenant_key"` + AssociateKey string `gorm:"type:varchar(50);not null" json:"associate_key"` sync.RWMutex base.Model } +// InstanceStatusEvent 审批实例状态表 +type InstanceStatusEvent struct { + InstanceID int64 `gorm:"type:bigint;not null" json:"instance_id"` + Status string `gorm:"type:varchar(50);not null" json:"status"` + Extension string `gorm:"type:varchar(50);not null" json:"extension"` + base.Model +} + +func (instance *ApprovalInstance) CheckIfComplete() bool { + currentSteps := instance.GetCurrentSteps() + if instance.GetStatus() == StatusCompleted { + return true + } + if len(currentSteps) == 1 && currentSteps[0].StepCode == flow_definition.StepEnd { + currentSteps[0].AddCompletedEvent() + instance.AddCompletedEvent() + return true + } + return false +} + +func (instance *ApprovalInstance) MarshalBinary() ([]byte, error) { + return json.Marshal(instance) +} + +func (instance *ApprovalInstance) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, instance) +} + func (instance *ApprovalInstance) GetNodes() []connect.AbstractNode { return utils.ConvertToAbstractNodes(instance.Steps, func(step *InstanceStep) connect.AbstractNode { return step @@ -41,24 +75,41 @@ func (instance *ApprovalInstance) GetNodes() []connect.AbstractNode { } func (instance *ApprovalInstance) SetNodes(nodes []connect.AbstractNode) { - //TODO implement me - panic("implement me") + specificType, _ := utils.ConvertToSpecificType[*InstanceStep, connect.AbstractNode](nodes, func(node connect.AbstractNode) (*InstanceStep, bool) { + return node.(*InstanceStep), true + }) + instance.Steps = specificType } func (instance *ApprovalInstance) GetNodeMap() map[string]connect.AbstractNode { - //TODO implement me - panic("implement me") + if instance.NodeMap == nil { + instance.NodeMap = map[string]connect.AbstractNode{} + connect.InitializeNodeMap(instance) + } + return instance.NodeMap } func (instance *ApprovalInstance) NewNodePathConfig(fromNodeKey, toNodeKey string) connect.AbstractNodePathConfig { - //TODO implement me - panic("implement me") + fromStep, err := instance.GetStepByKey(fromNodeKey) + if err != nil { + return nil + } + return NewDynamicPathConfig(instance.ID, fromStep.ID, fromNodeKey, toNodeKey, false) } // CurrentStep 目前审批中的节点 type CurrentStep struct { CurrentStepId int64 `gorm:"type:bigint;index;not null" json:"current_step_id"` InstanceID int64 `gorm:"type:bigint;index;not null" json:"instance_id"` + base.Model +} + +func (current *CurrentStep) MarshalBinary() ([]byte, error) { + return json.Marshal(current) +} + +func (current *CurrentStep) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, current) } // NewApprovalInstance 初始化创建一个审批流实例 @@ -78,6 +129,15 @@ func NewApprovalInstance( Steps: []*InstanceStep{}, Data: string(bytes), } + + // 保存租户键和关联键 + if value, ok := data[base.TenantKey]; ok { + instance.TenantKey = value.(string) + } + if value, ok := data[base.AssociateKey]; ok { + instance.TenantKey = value.(string) + } + // Get the first step of the approval flow firstStep, err := approvalFlow.FirstStep() if err != nil { @@ -85,15 +145,19 @@ func NewApprovalInstance( } // Build the steps recursively processedSteps := make(map[string]*InstanceStep) - err = buildInstanceSteps(ctx, instance, firstStep, nil, data, processedSteps) + err = buildInstanceSteps(ctx, approvalFlow, instance, firstStep, nil, data, processedSteps) if err != nil { return nil, err } + + // 赋予创建状态 + instance.AddCreatedEvent() return instance, nil } // buildInstanceSteps recursively builds instance steps from approval steps -func buildInstanceSteps(ctx *gin.Context, instance *ApprovalInstance, currentApprovalStep *flow_definition.ApprovalStep, +func buildInstanceSteps(ctx *gin.Context, approvalFlow *flow_definition.ApprovalFlow, + 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 { @@ -102,15 +166,21 @@ func buildInstanceSteps(ctx *gin.Context, instance *ApprovalInstance, currentApp } return nil } - // Create a new instance step + var approverKey string - approver, err := currentApprovalStep.DynamicConfig.GetApprover(ctx, data) - if err != nil { - approverKey = "" + if currentApprovalStep.DynamicConfig != nil { + approver, err := currentApprovalStep.DynamicConfig.GetApprover(ctx, data) + if err != nil { + approverKey = "" + } else { + approverKey = approver.Key + } } else { - approverKey = approver.Key + approverKey = "" } + currentInstanceStep := NewInstanceStep(currentApprovalStep, approverKey, false) + // Insert the instance step into the instance if previousInstanceStep == nil { connect.InsertNodeFirst(instance, currentInstanceStep) @@ -119,14 +189,16 @@ func buildInstanceSteps(ctx *gin.Context, instance *ApprovalInstance, currentApp } // Mark this step as processed processedSteps[currentApprovalStep.Key] = currentInstanceStep + // Get the next steps and recursively build them - nextSteps, err := connect.GetNextNodes(instance, currentApprovalStep) + flowStep, err := approvalFlow.GetStep(currentInstanceStep.StepID) + nextFlowSteps, err := connect.GetNextNodes(approvalFlow, flowStep) if err != nil { return err } - for _, nextStep := range nextSteps { + for _, nextStep := range nextFlowSteps { nextApprovalStep := nextStep.(*flow_definition.ApprovalStep) - err := buildInstanceSteps(ctx, instance, nextApprovalStep, currentInstanceStep, data, processedSteps) + err := buildInstanceSteps(ctx, approvalFlow, instance, nextApprovalStep, currentInstanceStep, data, processedSteps) if err != nil { return err } @@ -162,7 +234,7 @@ func (instance *ApprovalInstance) Start(ctx context.Context) error { instance.RLock() defer instance.RUnlock() - if instance.Status != StatusCreated { + if instance.GetStatus() != StatusCreated && instance.GetStatus() != StatusReject { return fmt.Errorf("审批流程已启动或已完成,无法再次启动") } @@ -174,7 +246,7 @@ func (instance *ApprovalInstance) Start(ctx context.Context) error { // 设置当前步骤为第一个步骤,并更改状态 instance.AddCurrentStepID(firstStep) - instance.Status = StatusInProgress + instance.AddInProgressEvent() err = instance.SendApprovalNotification(firstStep, ctx) if err != nil { @@ -210,19 +282,19 @@ func (instance *ApprovalInstance) MoveToNextStep() error { } currentStep := currentStepSlice[0] // 完成当前步骤 - currentStep.Status = StepStatusCompleted + currentStep.AddCompletedEvent() // 获取下一个步骤 var nextStep *InstanceStep nextStepArr, err := instance.getNextStep(currentStep, dataMap) - if nextStepArr != nil && len(nextStepArr) == 0 { + if nextStepArr != nil && len(nextStepArr) == 1 { nextStep = nextStepArr[0] } else { return errors.New("暂时不支持并行审批") } if err != nil { - instance.Status = StatusCompleted // 流程结束 + instance.AddCompletedEvent() // 流程结束 return nil } @@ -230,7 +302,8 @@ func (instance *ApprovalInstance) MoveToNextStep() error { instance.RemoveCurrentStepID(currentStep.ID) instance.AddCurrentStepID(nextStep) - nextStep.Status = StepStatusPending + nextStep.AddPendingEvent() + return nil } @@ -303,7 +376,7 @@ func (instance *ApprovalInstance) GetStepByKey(key string) (*InstanceStep, error return step, nil } } - return nil, fmt.Errorf("step with key %d not found in approval flow", key) + return nil, fmt.Errorf("step with key %s not found in approval flow", key) } // AddCurrentStepID adds a new CurrentStep to the CurrentStepIDs slice if it does not already exist @@ -324,6 +397,7 @@ func (instance *ApprovalInstance) RemoveCurrentStepID(stepID int64) { for i, s := range instance.CurrentStepIDs { if s.CurrentStepId == stepID { // Remove the step by slicing + s.Delete() instance.CurrentStepIDs = append(instance.CurrentStepIDs[:i], instance.CurrentStepIDs[i+1:]...) return } @@ -343,7 +417,7 @@ func (instance *ApprovalInstance) HasCurrentStepID(stepID int64) bool { // GetCurrentSteps returns a copy of the CurrentSteps slice func (instance *ApprovalInstance) GetCurrentSteps() []*InstanceStep { // Return a copy of the slice to avoid modifications - currentSteps := make([]*InstanceStep, len(instance.CurrentStepIDs)) + var currentSteps []*InstanceStep for _, step := range instance.Steps { if instance.HasCurrentStepID(step.ID) { currentSteps = append(currentSteps, step) @@ -362,9 +436,60 @@ func (instance *ApprovalInstance) GetCurrentStepsById(id int64) *InstanceStep { return nil } -// executeApprovalStep 执行审批步骤 -func (instance *ApprovalInstance) executeApprovalStep(approvalFlow flow_definition.ApprovalFlow) error { - for _, instanceStep := range instance.GetCurrentSteps() { +func (instance *ApprovalInstance) ExecuteApprovalReversal(reversal *ApprovalReversal) error { + step, err := instance.GetStepByKey(reversal.StepKey) + if err != nil { + return err + } + currentStep := instance.GetCurrentStepsById(step.ID) + if currentStep == nil { + return errors.New("当前反转节点不是正在审批的节点") + } + switch reversal.FixAction { + case FixActionReApproveAndUpdateData: + err := currentStep.Reject(reversal.Reason) + if err != nil { + return err + } + // 移除当前所有节点,替换为开始节点 + for _, step := range instance.GetCurrentSteps() { + instance.RemoveCurrentStepID(step.ID) + } + reversalStep, err := instance.GetStepByKey(reversal.ReversedStepKey) + if err != nil { + return err + } + + for _, step := range instance.Steps { + // 所有节点重新加入待审批事件 + if step.GetStatus() == StepStatusApproved { + // 如果是通过的话要再审批 + step.AddPendingEvent() + } + } + + instance.AddCurrentStepID(reversalStep) + } + return nil +} + +func (instance *ApprovalInstance) Reversal(step *InstanceStep, reversedStep *InstanceStep, reason string, fixAction string) error { + reversal := NewReversal(step, reversedStep, reason, fixAction) + step.addApprovalReversal(reversal) + instance.AddRejectEvent() + return instance.ExecuteApprovalReversal(reversal) +} + +// ExecuteApprovalStep 执行审批步骤规则 +func (instance *ApprovalInstance) ExecuteApprovalStep(approvalFlow *flow_definition.ApprovalFlow) error { + currentSteps := instance.GetCurrentSteps() + for _, instanceStep := range currentSteps { + + // 只对待审批的节点进行处理 + if instanceStep.GetStatus() != StepStatusPending { + continue + } + flowStep, err := approvalFlow.GetStep(instanceStep.StepID) if err != nil { continue @@ -415,11 +540,88 @@ func (instance *ApprovalInstance) executeApprovalStep(approvalFlow flow_definiti } } } + return instance.CheckIfMoveToNextStep(currentSteps) +} - return nil // 如果没有规则满足,正常返回 +func (instance *ApprovalInstance) CheckIfMoveToNextStep(currentSteps []*InstanceStep) error { + if instance.CheckIfComplete() { + return errors.New("当前审批流已结束") + } + + // 如果当前节点所有的处理完毕,那么当前节点将向后移动 + currentIsCompleted := true + for _, step := range currentSteps { + if step.GetStatus() != StepStatusCompleted && step.GetStatus() != StepStatusApproved { + currentIsCompleted = false + } + } + if currentIsCompleted { + err := instance.MoveToNextStep() + if err != nil { + return err + } + instance.CheckIfComplete() + return nil + } + return nil } func (instance *ApprovalInstance) GetPathByFromStepKey(fromStepKey string) (*InstancePathConfig, error) { return nil, nil } + +func (instance *ApprovalInstance) GetStartStep() *InstanceStep { + for _, step := range instance.Steps { + if step.StepCode == flow_definition.StepStart { + return step + } + } + return nil +} + +func (instance *ApprovalInstance) GetEndStep() *InstanceStep { + for _, step := range instance.Steps { + if step.StepCode == flow_definition.StepEnd { + return step + } + } + return nil +} + +func (instance *ApprovalInstance) GetStatus() string { + return instance.StatusEvents[len(instance.StatusEvents)-1].Status +} + +func (instance *ApprovalInstance) AddStatusEvent(status string) { + if instance.StatusEvents == nil { + instance.StatusEvents = make([]*InstanceStatusEvent, 0) + } + if len(instance.StatusEvents) > 0 && instance.GetStatus() == status { + return + } + instance.StatusEvents = append(instance.StatusEvents, &InstanceStatusEvent{ + InstanceID: instance.ID, Status: status, Extension: "", + }) + instance.Status = status +} + +func (instance *ApprovalInstance) AddCreatedEvent() { + instance.AddStatusEvent(StatusCreated) +} + +func (instance *ApprovalInstance) AddRejectEvent() { + instance.AddStatusEvent(StatusReject) +} + +func (instance *ApprovalInstance) AddInProgressEvent() { + instance.AddStatusEvent(StatusInProgress) +} + +func (instance *ApprovalInstance) AddCompletedEvent() { + instance.AddStatusEvent(StatusCompleted) +} + +func (instance *ApprovalInstance) AddCancelledEvent() { + instance.AddStatusEvent(StatusCancelled) +} diff --git a/app/provider/flow_instance/model/approval_record.go b/app/provider/flow_instance/model/approval_record.go index ac96e46..b5fa8e8 100644 --- a/app/provider/flow_instance/model/approval_record.go +++ b/app/provider/flow_instance/model/approval_record.go @@ -2,6 +2,7 @@ package model import ( "approveflow/app/base" + "encoding/json" ) const ( @@ -21,6 +22,14 @@ type ApprovalRecord struct { base.Model } +func (record *ApprovalRecord) MarshalBinary() ([]byte, error) { + return json.Marshal(record) +} + +func (record *ApprovalRecord) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, record) +} + // CreateRecord 创建新的审批记录 func (record *ApprovalRecord) CreateRecord(stepID int64, approverKey, status, comments string) { record.InstanceStepID = stepID diff --git a/app/provider/flow_instance/model/approval_reversal.go b/app/provider/flow_instance/model/approval_reversal.go index 6051584..9ab46a3 100644 --- a/app/provider/flow_instance/model/approval_reversal.go +++ b/app/provider/flow_instance/model/approval_reversal.go @@ -1,28 +1,48 @@ package model -import "approveflow/app/base" +import ( + "approveflow/app/base" + "encoding/json" +) // ApprovalReversal 审批反转表 type ApprovalReversal struct { - InstanceID int64 `gorm:"index;not null" json:"instance_id"` // 关联的审批实例ID - StepID int64 `gorm:"index;not null" json:"step_id"` // 关联的步骤ID - ReversedStepID int64 `gorm:"index;not null" json:"reversed_step_id"` // 反转的步骤ID - FixAction string `gorm:"type:varchar(100);not null" json:"fix_action"` // 修正动作 - Reason string `gorm:"type:text" json:"reason"` // 反转原因 + InstanceID int64 `gorm:"type:bigint;index;not null" json:"instance_id"` // 关联的审批实例ID + StepID int64 `gorm:"type:bigint;index;not null" json:"step_id"` // 关联的步骤ID + StepKey string `gorm:"type:varchar(50);index;not null" json:"step_key"` // 关联的步骤ID + ReversedStepKey string `gorm:"type:varchar(50);index;not null" json:"reversed_step_id"` // 反转的步骤ID + FixAction string `gorm:"type:varchar(100);not null" json:"fix_action"` // 修正动作 + Reason string `gorm:"type:text" json:"reason"` // 反转原因 base.Model } -// ReverseStep 执行步骤的反转操作 -func (reversal *ApprovalReversal) ReverseStep(step *InstanceStep, reason string) error { - reversal.InstanceID = step.InstanceID - reversal.StepID = step.StepID - reversal.ReversedStepID = step.ID - reversal.Reason = reason - reversal.FixAction = "Reversal" +// FixAction 定义常量表示审批反转的修正动作 +const ( + FixActionReApproveAndUpdateData = "ReApproveAndUpdateData" + FixActionReApprove = "ReApprove" // 重新审批 + FixActionReassignApprover = "ReassignApprover" // 重新指定审批人 + FixActionRollback = "Rollback" // 回滚到某个步骤 + FixActionUpdateData = "UpdateData" // 更新审批数据 + FixActionReopen = "Reopen" // 重新打开审批流程 +) - // 更新步骤状态为反转 - step.Status = "Reversed" - return nil +func (reversal *ApprovalReversal) MarshalBinary() ([]byte, error) { + return json.Marshal(reversal) +} + +func (reversal *ApprovalReversal) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, reversal) +} + +func NewReversal(step *InstanceStep, reversedStep *InstanceStep, reason string, fixAction string) *ApprovalReversal { + reversal := &ApprovalReversal{ + StepID: step.ID, + StepKey: step.Key, + ReversedStepKey: reversedStep.Key, + FixAction: fixAction, + Reason: reason, + } + return reversal } // FixIssue 执行修正操作 diff --git a/app/provider/flow_instance/model/batch_approval_task.go b/app/provider/flow_instance/model/batch_approval_task.go index 5451397..6b6468f 100644 --- a/app/provider/flow_instance/model/batch_approval_task.go +++ b/app/provider/flow_instance/model/batch_approval_task.go @@ -2,6 +2,7 @@ package model import ( "approveflow/app/base" + "encoding/json" "time" ) @@ -15,3 +16,17 @@ type BatchApprovalTask struct { ActionTime time.Time `json:"action_time"` // 审批时间 base.Model } + +type BatchApprovalTaskContent struct { + InstanceID int64 + InstanceStepID int64 + base.Model +} + +func (task *BatchApprovalTask) MarshalBinary() ([]byte, error) { + return json.Marshal(task) +} + +func (task *BatchApprovalTask) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, task) +} diff --git a/app/provider/flow_instance/model/instance_path_config.go b/app/provider/flow_instance/model/instance_path_config.go index d52a653..9fc7ef3 100644 --- a/app/provider/flow_instance/model/instance_path_config.go +++ b/app/provider/flow_instance/model/instance_path_config.go @@ -3,6 +3,7 @@ package model import ( "approveflow/app/base" "approveflow/app/provider/flow_definition" + "encoding/json" ) // InstancePathConfig 动态路径配置表结构 @@ -18,19 +19,27 @@ type InstancePathConfig struct { base.Model } -func (d InstancePathConfig) GetKey() string { +func (d *InstancePathConfig) MarshalBinary() ([]byte, error) { + return json.Marshal(d) +} + +func (d *InstancePathConfig) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, d) +} + +func (d *InstancePathConfig) GetKey() string { return d.Key } -func (d InstancePathConfig) GetNodeID() int64 { +func (d *InstancePathConfig) GetNodeID() int64 { return d.NodeID } -func (d InstancePathConfig) GetFromNodeKey() string { +func (d *InstancePathConfig) GetFromNodeKey() string { return d.FromNodeKey } -func (d InstancePathConfig) GetToNodeKey() string { +func (d *InstancePathConfig) GetToNodeKey() string { return d.ToNodeKey } @@ -47,11 +56,11 @@ func NewDynamicPathConfig(instanceID, nodeId int64, FromStepKey, ToStepKey strin } // IsConditionMet 判断路径条件是否满足 -func (a *InstancePathConfig) IsConditionMet(data map[string]interface{}) (bool, error) { - if a.ConditionExpression == "" { +func (d *InstancePathConfig) IsConditionMet(data map[string]interface{}) (bool, error) { + if d.ConditionExpression == "" { return true, nil // 无条件,默认满足 } - result, err := a.EvaluateCondition(data) + result, err := d.EvaluateCondition(data) if err != nil { return false, err } diff --git a/app/provider/flow_instance/model/instance_step.go b/app/provider/flow_instance/model/instance_step.go index 381deb3..8e8d28d 100644 --- a/app/provider/flow_instance/model/instance_step.go +++ b/app/provider/flow_instance/model/instance_step.go @@ -5,6 +5,7 @@ import ( "approveflow/app/provider/abstract/connect" "approveflow/app/provider/flow_definition" "approveflow/app/utils" + "encoding/json" "fmt" "github.com/google/uuid" ) @@ -19,20 +20,81 @@ const ( // 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"` // 审批记录 + 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:"-" json:"status"` // 审批状态 + ApproverComments string `gorm:"type:text" json:"approver_comments"` // 审批意见 + IsDynamic bool `gorm:"not null;default:false" json:"is_dynamic"` // 是否为动态步骤 + StatusEvents []*InstanceStepStatusEvent `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"status_events"` // 状态事件表 + InstancePathConfigs []*InstancePathConfig `gorm:"foreignKey:NodeID;constraint:OnDelete:CASCADE" json:"instance_path_configs"` // 动态路径配置,自定义,不直接存储 + Records []*ApprovalRecord `gorm:"foreignKey:InstanceStepID;constraint:OnDelete:CASCADE" json:"records"` // 审批记录 + Reversals []*ApprovalReversal `gorm:"foreignKey:StepID;constraint:OnDelete:CASCADE" json:"reversals"` // 反转记录 base.Model } +// InstanceStepStatusEvent 审批步骤状态表 +type InstanceStepStatusEvent struct { + StepID int64 `gorm:"type:bigint;not null" json:"step_id"` + Status string `gorm:"type:varchar(50);not null" json:"status"` + Extension string `gorm:"type:varchar(50);not null" json:"extension"` + base.Model +} + +type ByCreatedAt []*InstanceStepStatusEvent + +func (a ByCreatedAt) Len() int { return len(a) } +func (a ByCreatedAt) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByCreatedAt) Less(i, j int) bool { return a[i].CreatedAt.After(a[j].CreatedAt) } + +func (step *InstanceStep) GetStatus() string { + return step.StatusEvents[len(step.StatusEvents)-1].Status +} + +func (step *InstanceStep) AddStatusEvent(status string) { + if step.StatusEvents == nil { + step.StatusEvents = make([]*InstanceStepStatusEvent, 0) + } + if len(step.StatusEvents) > 0 && step.GetStatus() == status { + return + } + step.StatusEvents = append(step.StatusEvents, &InstanceStepStatusEvent{ + StepID: step.ID, Status: status, Extension: "", + }) + step.Status = status +} + +func (step *InstanceStep) AddPendingEvent() { + step.AddStatusEvent(StepStatusPending) +} + +func (step *InstanceStep) AddApprovedEvent() { + step.AddStatusEvent(StepStatusApproved) +} + +func (step *InstanceStep) AddRejectedEvent() { + step.AddStatusEvent(StepStatusRejected) +} + +func (step *InstanceStep) AddReversedEvent() { + step.AddStatusEvent(StepStatusReversed) +} + +func (step *InstanceStep) AddCompletedEvent() { + step.AddStatusEvent(StepStatusCompleted) +} + +func (step *InstanceStep) MarshalBinary() ([]byte, error) { + return json.Marshal(step) +} + +func (step *InstanceStep) UnmarshalBinary(data []byte) error { + return json.Unmarshal(data, step) +} + func (step *InstanceStep) GetPathConfigs() []connect.AbstractNodePathConfig { return utils.ConvertToAbstractNodes(step.InstancePathConfigs, func(t *InstancePathConfig) connect.AbstractNodePathConfig { return t @@ -60,19 +122,19 @@ func NewInstanceStep(flowStep *flow_definition.ApprovalStep, approverKey string, StepName: flowStep.Name, StepCode: flowStep.StepCode, ApproverKey: approverKey, - Status: StepStatusPending, IsDynamic: isDynamic, } + instanceStep.AddPendingEvent() instanceStep.Key = uuid.New().String() return instanceStep } // Approve 审批通过 func (step *InstanceStep) Approve(comments string) error { - if step.Status != StepStatusPending { + if step.GetStatus() != StepStatusPending { return fmt.Errorf("当前步骤不是待审批状态") } - step.Status = StepStatusApproved + step.AddApprovedEvent() step.ApproverComments = comments step.addApprovalRecord(StepStatusApproved, comments) return nil @@ -80,10 +142,10 @@ func (step *InstanceStep) Approve(comments string) error { // Reject 审批驳回 func (step *InstanceStep) Reject(comments string) error { - if step.Status != StepStatusPending { - return fmt.Errorf("当前步骤不是待审批状态") - } - step.Status = StepStatusRejected + //if step.GetStatus() != StepStatusPending { + // return fmt.Errorf("当前步骤不是待审批状态") + //} + step.AddRejectedEvent() step.ApproverComments = comments step.addApprovalRecord(StepStatusRejected, comments) return nil @@ -99,3 +161,9 @@ func (step *InstanceStep) addApprovalRecord(status string, comments string) { } step.Records = append(step.Records, record) } + +func (step *InstanceStep) addApprovalReversal(reversal *ApprovalReversal) { + var reversals []*ApprovalReversal + reversals = append(reversals, reversal) + step.Reversals = reversals +} diff --git a/app/provider/flow_instance/model/model_test.go b/app/provider/flow_instance/model/model_test.go new file mode 100644 index 0000000..ed35424 --- /dev/null +++ b/app/provider/flow_instance/model/model_test.go @@ -0,0 +1,24 @@ +package model + +import ( + "approveflow/app/provider/database_connect" + tests "approveflow/test" + "testing" +) + +func TestModel(t *testing.T) { + + container := tests.InitBaseContainer() + databaseConnectService := container.MustMake(database_connect.DatabaseConnectKey).(database_connect.Service) + + db := databaseConnectService.DefaultDatabaseConnect() + db.AutoMigrate(ApprovalInstance{}, + InstanceStatusEvent{}, + ApprovalRecord{}, + ApprovalReversal{}, + BatchApprovalTask{}, + CurrentStep{}, + InstancePathConfig{}, + InstanceStep{}, + InstanceStepStatusEvent{}) +} diff --git a/app/provider/flow_instance/service.go b/app/provider/flow_instance/service.go index f71c618..7209a32 100644 --- a/app/provider/flow_instance/service.go +++ b/app/provider/flow_instance/service.go @@ -29,6 +29,19 @@ func (f *FlowInstanceService) CreateInstance(ctx *gin.Context, approvalFlow *flo func (f *FlowInstanceService) StartInstance(ctx context.Context, instanceID int64) (*model.ApprovalInstance, error) { return f.handlerInstance(ctx, instanceID, func(ctx context.Context, instance *model.ApprovalInstance) error { err := instance.Start(ctx) + flowService := f.container.MustMake(flow_definition.FlowDefinitionKey).(flow_definition.Service) + flow, err := flowService.GetFlow(ctx, instance.FlowID) + err = instance.ExecuteApprovalStep(flow) + err = instance.CheckIfMoveToNextStep(instance.GetCurrentSteps()) + return err + }) +} + +func (f *FlowInstanceService) StartInstanceRule(ctx context.Context, instanceID int64) (*model.ApprovalInstance, error) { + return f.handlerInstance(ctx, instanceID, func(ctx context.Context, instance *model.ApprovalInstance) error { + flowService := f.container.MustMake(flow_definition.FlowDefinitionKey).(flow_definition.Service) + flow, err := flowService.GetFlow(ctx, instance.FlowID) + err = instance.ExecuteApprovalStep(flow) return err }) } @@ -42,13 +55,21 @@ func (f *FlowInstanceService) GetInstancePage(ctx context.Context, pageNum, page } func (f *FlowInstanceService) CancelInstance(ctx context.Context, instanceID int64) error { - //TODO implement me - panic("implement me") + _, err := f.handlerInstance(ctx, instanceID, func(ctx context.Context, instance *model.ApprovalInstance) error { + if instance.GetStatus() == model.StatusInProgress || instance.GetStatus() == model.StatusCreated { + instance.AddCancelledEvent() + return nil + } + return errors.New("instance is cancelled or Completed") + }) + return err } func (f *FlowInstanceService) ApproveStep(ctx context.Context, instanceID int64, stepID int64, comments string) error { return f.handlerStep(ctx, instanceID, stepID, func(ctx context.Context, instance *model.ApprovalInstance, step *model.InstanceStep) error { - err := step.Approve(comments) + err := instance.CheckIfMoveToNextStep(instance.GetCurrentSteps()) + err = step.Approve(comments) + err = instance.CheckIfMoveToNextStep(instance.GetCurrentSteps()) if err != nil { return err } @@ -58,7 +79,8 @@ func (f *FlowInstanceService) ApproveStep(ctx context.Context, instanceID int64, func (f *FlowInstanceService) RejectStep(ctx context.Context, instanceID int64, stepID int64, comments string) error { return f.handlerStep(ctx, instanceID, stepID, func(ctx context.Context, instance *model.ApprovalInstance, step *model.InstanceStep) error { - err := step.Reject(comments) + err := instance.CheckIfMoveToNextStep(instance.GetCurrentSteps()) + err = instance.Reversal(step, instance.GetStartStep(), comments, model.FixActionReApproveAndUpdateData) if err != nil { return err } @@ -150,6 +172,11 @@ func (f *FlowInstanceService) handlerInstance(ctx context.Context, instanceID in if err != nil { return nil, err } + + err = f.GetFlowInstanceRepository().SaveInstance(ctx, instance) + if err != nil { + return nil, err + } return instance, nil } diff --git a/app/provider/user/service.go b/app/provider/user/service.go index cb4c62c..72f0bb1 100644 --- a/app/provider/user/service.go +++ b/app/provider/user/service.go @@ -6,6 +6,26 @@ type UserService struct { container framework.Container } +func (s *UserService) GetDirectSupervisorByUserId(userId int64) (*User, error) { + return &User{ + ID: 1, + UserName: "dandan", + Name: "卢麟哲", + EmployeeNo: "230615020", + }, nil +} + +func (s *UserService) GetDirectSupervisorByUserKey(userKey string) (*User, error) { + user := &User{ + ID: 1, + UserName: "dandan", + Name: "卢麟哲", + EmployeeNo: "230615020", + } + user.Key = "230615020" + return user, nil +} + func NewUserService(params ...interface{}) (interface{}, error) { container := params[0].(framework.Container) return &UserService{container: container}, nil