diff --git a/flow-engine-example/src/main/java/com/codingapi/example/entity/User.java b/flow-engine-example/src/main/java/com/codingapi/example/entity/User.java index 6f3377fb..f73b939b 100644 --- a/flow-engine-example/src/main/java/com/codingapi/example/entity/User.java +++ b/flow-engine-example/src/main/java/com/codingapi/example/entity/User.java @@ -65,6 +65,9 @@ public boolean isFlowManager() { @Override public IFlowOperator forwardOperator() { - return GatewayContext.getInstance().getFlowOperator(flowOperatorId); + if(flowOperatorId!=null && flowOperatorId > 0){ + return GatewayContext.getInstance().getFlowOperator(flowOperatorId); + } + return null; } } diff --git a/flow-engine-example/src/main/resources/.gitignore b/flow-engine-example/src/main/resources/.gitignore new file mode 100644 index 00000000..93a464bb --- /dev/null +++ b/flow-engine-example/src/main/resources/.gitignore @@ -0,0 +1 @@ +static/ \ No newline at end of file diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java index c786cbbc..430ee828 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/node/nodes/EndNode.java @@ -42,9 +42,7 @@ public List generateCurrentRecords(FlowSession session) { @Override public void fillNewRecord(FlowSession session, FlowRecord flowRecord) { - flowRecord.setTitle("over"); - flowRecord.setCurrentOperatorId(-1); - + flowRecord.over(); IFlowAction currentAction = session.getCurrentAction(); // 标记当前流程结束 FlowRecord latestRecord = session.getCurrentRecord(); diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java index c49304ee..adb9289c 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowActionRequest.java @@ -31,6 +31,13 @@ public class FlowActionRequest { private FlowAdviceBody advice; + public void updateOperatorId(long operatorId) { + if (advice != null) { + advice.setOperatorId(operatorId); + } + } + + public FlowAdvice toFlowAdvice(Workflow workflow, IFlowAction flowAction) { FlowAdvice flowAdvice = new FlowAdvice(); flowAdvice.setAdvice(advice.getAdvice()); diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowCreateRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowCreateRequest.java index 43dd37bc..74712de4 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowCreateRequest.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowCreateRequest.java @@ -31,6 +31,12 @@ public class FlowCreateRequest { */ private long operatorId; + /** + * 父流程id + */ + private long parentRecordId; + + public FlowActionRequest toActionRequest(long recordId) { FlowActionRequest flowActionRequest = new FlowActionRequest(); flowActionRequest.setFormData(this.getFormData()); @@ -56,4 +62,7 @@ public void verify() { } } + public boolean isSubProcess() { + return this.parentRecordId!=0; + } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowDetailRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowDetailRequest.java new file mode 100644 index 00000000..4cb705a1 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowDetailRequest.java @@ -0,0 +1,34 @@ +package com.codingapi.flow.pojo.request; + +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * 流程详情请求 + */ +@Data +@NoArgsConstructor +public class FlowDetailRequest { + /** + * 详情id,可以是workId或者是recordId + */ + private String id; + /** + * 流程的操作人Id + */ + private long operatorId; + + public boolean isCreateWorkflow() { + return !id.matches("^[0-9]+$"); + } + + public FlowDetailRequest(long id,long operatorId){ + this.id = String.valueOf(id); + this.operatorId = operatorId; + } + + public FlowDetailRequest(String id, long operatorId) { + this.id = id; + this.operatorId = operatorId; + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowProcessNodeRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowProcessNodeRequest.java new file mode 100644 index 00000000..9f1c8c95 --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowProcessNodeRequest.java @@ -0,0 +1,44 @@ +package com.codingapi.flow.pojo.request; + +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.Map; + +/** + * 流程节点记录请求 + */ +@Data +@NoArgsConstructor +public class FlowProcessNodeRequest { + /** + * 详情id,可以是workId或者是recordId + */ + private String id; + + /** + * 流程的操作人Id + */ + private long operatorId; + + /** + * 表单数据 + */ + private Map formData; + + public FlowProcessNodeRequest(long id, long operatorId, Map formData) { + this.id = String.valueOf(id); + this.operatorId = operatorId; + this.formData = formData; + } + + public FlowProcessNodeRequest(String id, long operatorId, Map formData) { + this.id = id; + this.operatorId = operatorId; + this.formData = formData; + } + + public boolean isCreateWorkflow() { + return !id.matches("^[0-9]+$"); + } +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowRevokeRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowRevokeRequest.java index 11ec0a60..b4e44384 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowRevokeRequest.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowRevokeRequest.java @@ -5,6 +5,9 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * 流程撤回请求 + */ @Data @NoArgsConstructor @AllArgsConstructor diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowUrgeRequest.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowUrgeRequest.java index dfd427bd..810a9bda 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowUrgeRequest.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/request/FlowUrgeRequest.java @@ -5,6 +5,9 @@ import lombok.Data; import lombok.NoArgsConstructor; +/** + * 流程催办请求 + */ @Data @NoArgsConstructor @AllArgsConstructor diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/FlowContent.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/FlowContent.java index 6199f34c..de7938d0 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/FlowContent.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/FlowContent.java @@ -2,13 +2,11 @@ import com.codingapi.flow.action.IFlowAction; import com.codingapi.flow.form.FormMeta; +import com.codingapi.flow.manager.ActionManager; import com.codingapi.flow.manager.NodeStrategyManager; -import com.codingapi.flow.manager.OperatorManager; import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.operator.IFlowOperator; import com.codingapi.flow.record.FlowRecord; -import com.codingapi.flow.session.FlowSession; -import com.codingapi.flow.strategy.node.OperatorLoadStrategy; import com.codingapi.flow.workflow.Workflow; import lombok.Data; @@ -26,15 +24,31 @@ public class FlowContent { * 流程记录编号 */ private long recordId; + /** * 流程编号 */ - private String workflowCode; + private String workId; + + /** + * 流程编码 + */ + private String workCode; /** * 流程视图 */ private String view; + /** + * 审批意见是否必填 + */ + private boolean adviceNullable; + + /** + * 签名是否必填 + */ + private boolean signable; + /** * 表单元数据 */ @@ -79,37 +93,21 @@ public class FlowContent { */ private List histories; - /** - * 下一审批 - */ - private List nextNodes; - - public void pushNextNodes(FlowSession flowSession, List nextNodes) { - List nextNodeList = new ArrayList<>(); - for (IFlowNode node : nextNodes){ - NextNode nextNode = new NextNode(); - nextNode.setNodeId(node.getId()); - nextNode.setNodeName(node.getName()); - nextNode.setNodeType(node.getType()); - - NodeStrategyManager nodeStrategyManager = node.strategyManager(); - OperatorManager operatorManager = nodeStrategyManager.loadOperators(flowSession); - nextNode.setOperators(operatorManager.getOperators().stream().map(FlowOperator::new).toList()); - - nextNodeList.add(nextNode); - } - this.nextNodes = nextNodeList; - } public void pushCurrentNode(IFlowNode currentNode) { - this.actions = currentNode.actionManager().getActions(); + ActionManager actionManager = currentNode.actionManager(); + NodeStrategyManager strategyManager = currentNode.strategyManager(); + this.actions = actionManager.getActions(); + this.adviceNullable = strategyManager.isEnableAdvice(); + this.signable = strategyManager.isEnableSignable(); Map nodeData = currentNode.toMap(); this.view = (String) nodeData.get("view"); } public void pushWorkflow(Workflow workflow) { this.form = workflow.getForm(); - this.workflowCode = workflow.getCode(); + this.workCode = workflow.getCode(); + this.workId = workflow.getId(); } public void pushRecords(FlowRecord record, List mergeRecords) { @@ -155,30 +153,6 @@ public void pushCurrentOperator(IFlowOperator currentOperator) { } - /** - * 流程图 - */ - @Data - public static class NextNode{ - /** - * 节点名称 - */ - private String nodeId; - /** - * 节点名称 - */ - private String nodeName; - /** - * 节点类型 - */ - private String nodeType; - - /** - * 节点审批人 - */ - private List operators; - } - @Data public static class History{ /** diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java new file mode 100644 index 00000000..2dd5028b --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/pojo/response/ProcessNode.java @@ -0,0 +1,102 @@ +package com.codingapi.flow.pojo.response; + +import com.codingapi.flow.node.IFlowNode; +import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.workflow.Workflow; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +/** + * 流程审批节点 + */ +@Data +@NoArgsConstructor +public class ProcessNode { + /** + * 节点名称 + */ + private String nodeId; + /** + * 节点名称 + */ + private String nodeName; + /** + * 节点类型 + */ + private String nodeType; + + /** + * 是否历史记录 + */ + private boolean history; + + /** + * 节点审批人 + */ + private List operators; + + + public ProcessNode(FlowRecord flowRecord, Workflow workflow) { + this.nodeId = flowRecord.getNodeId(); + IFlowNode flowNode = workflow.getFlowNode(this.nodeId); + this.nodeName = flowNode.getName(); + this.nodeType = flowNode.getType(); + this.operators = new ArrayList<>(); + this.history = true; + this.operators.add(new FlowOperatorBody(flowRecord)); + } + + + public ProcessNode(IFlowNode flowNode, List operators) { + this.nodeId = flowNode.getId(); + this.nodeName = flowNode.getName(); + this.nodeType = flowNode.getType(); + this.operators = operators.stream().map(FlowOperatorBody::new).toList(); + this.history = false; + } + + + /** + * 审批意见内容,仅当历史节点存在数据 + */ + @Data + @NoArgsConstructor + public static class FlowOperatorBody { + + /** + * 审批意见 + */ + private String advice; + + /** + * 签名key + */ + private String signKey; + + /** + * 审批记录 + */ + private FlowOperator flowOperator; + /** + * 审批时间 + */ + private long approveTime; + + public FlowOperatorBody(FlowRecord flowRecord) { + this.advice = flowRecord.getAdvice(); + this.signKey = flowRecord.getSignKey(); + this.approveTime = flowRecord.getCreateTime(); + this.flowOperator = new FlowOperator(flowRecord.getCurrentOperatorId(), flowRecord.getCurrentOperatorName()); + } + + public FlowOperatorBody(IFlowOperator flowOperator) { + this.flowOperator = new FlowOperator(flowOperator); + } + + } + +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java index ca3fe795..d9a3e693 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowRecord.java @@ -16,7 +16,6 @@ import lombok.Setter; import org.springframework.util.StringUtils; -import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -60,10 +59,21 @@ public class FlowRecord { * 节点类型 */ private String nodeType; + /** - * 父节点id + * 节点名称 + */ + private String nodeName; + + /** + * 来源id */ private long fromId; + /** + * 父节点id(用于子流程中) + */ + private long parentId; + /** * 表单数据 */ @@ -250,6 +260,7 @@ public FlowRecord(FlowSession flowSession, int nodeOrder) { this.workBackupId = flowSession.getBackupId(); this.nodeId = flowSession.getCurrentNodeId(); this.nodeType = flowSession.getCurrentNodeType(); + this.nodeName = flowSession.getCurrentNodeName(); this.formData = flowSession.getFormData().toMapData(); this.nodeOrder = nodeOrder; this.processId = RandomUtils.generateStringId(); @@ -295,6 +306,7 @@ public void extendsRecord(FlowRecord record) { this.fromId = record.id; this.processId = record.processId; this.delegateId = record.delegateId; + this.parentId = record.getParentId(); } } @@ -557,7 +569,10 @@ public boolean isForward() { * @param advice 节点审批信息 * @return FlowSession */ - public FlowSession createFlowSession( Workflow workflow,IFlowOperator currentOperator,FormData formData, FlowAdvice advice) { + public FlowSession createFlowSession( Workflow workflow, + IFlowOperator currentOperator, + FormData formData, + FlowAdvice advice) { List currentRecords = RepositoryHolderContext.getInstance().findCurrentNodeRecords(this.getFromId(), this.getNodeId()); IFlowNode currentNode = workflow.getFlowNode(nodeId); return new FlowSession( @@ -572,4 +587,21 @@ public FlowSession createFlowSession( Workflow workflow,IFlowOperator currentOpe advice ); } + + /** + * 设置为已读 + */ + public void read() { + this.readTime = System.currentTimeMillis(); + } + + /** + * 流程结束 + */ + public void over() { + this.title = "-"; + this.readTime = System.currentTimeMillis(); + this.currentOperatorId = -1; + this.recordState = SATE_RECORD_DONE; + } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowTodoRecord.java b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowTodoRecord.java index 3a15f59c..e128f54b 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowTodoRecord.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/record/FlowTodoRecord.java @@ -40,6 +40,11 @@ public class FlowTodoRecord { */ private String nodeType; + /** + * 节点名称 + */ + private String nodeName; + /** * 消息标题 */ @@ -113,6 +118,7 @@ public void update(FlowRecord flowRecord) { this.workCode = flowRecord.getWorkCode(); this.nodeId = flowRecord.getNodeId(); this.nodeType = flowRecord.getNodeType(); + this.nodeName = flowRecord.getNodeName(); this.title = flowRecord.getTitle(); this.readTime = flowRecord.getReadTime(); this.currentOperatorId = flowRecord.getCurrentOperatorId(); diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/script/node/SubProcessScript.java b/flow-engine-framework/src/main/java/com/codingapi/flow/script/node/SubProcessScript.java index 558e873a..fa5a43d6 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/script/node/SubProcessScript.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/script/node/SubProcessScript.java @@ -1,6 +1,7 @@ package com.codingapi.flow.script.node; import com.codingapi.flow.pojo.request.FlowCreateRequest; +import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.script.runtime.ScriptRuntimeContext; import com.codingapi.flow.session.FlowSession; import lombok.AllArgsConstructor; @@ -18,7 +19,10 @@ public class SubProcessScript { private final String script; public FlowCreateRequest execute(FlowSession request) { - return ScriptRuntimeContext.getInstance().run(script, FlowCreateRequest.class, request); + FlowRecord flowRecord = request.getCurrentRecord(); + FlowCreateRequest flowCreateRequest = ScriptRuntimeContext.getInstance().run(script, FlowCreateRequest.class, request); + flowCreateRequest.setParentRecordId(flowRecord.getId()); + return flowCreateRequest; } public static SubProcessScript defaultScript() { diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java index 79363a7e..bba31d87 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/FlowService.java @@ -2,18 +2,19 @@ import com.codingapi.flow.context.RepositoryHolderContext; import com.codingapi.flow.gateway.FlowOperatorGateway; -import com.codingapi.flow.operator.IFlowOperator; -import com.codingapi.flow.pojo.request.FlowActionRequest; -import com.codingapi.flow.pojo.request.FlowCreateRequest; -import com.codingapi.flow.pojo.request.FlowRevokeRequest; -import com.codingapi.flow.pojo.request.FlowUrgeRequest; +import com.codingapi.flow.pojo.request.*; import com.codingapi.flow.pojo.response.FlowContent; +import com.codingapi.flow.pojo.response.ProcessNode; import com.codingapi.flow.repository.*; import com.codingapi.flow.service.impl.*; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; /** * 流程服务 */ +@Transactional public class FlowService { public FlowService(WorkflowRepository workflowRepository, @@ -39,15 +40,26 @@ public FlowService(WorkflowRepository workflowRepository, /** * 流程详情 - * @param id 流程id 或者workflowCode - * @param currentOperator 当前操作人 + * @param request 流程详情请求 * @return 流程详情 */ - public FlowContent detail(String id,IFlowOperator currentOperator) { - FlowDetailService flowDetailService = new FlowDetailService(id,currentOperator); + public FlowContent detail(FlowDetailRequest request) { + FlowDetailService flowDetailService = new FlowDetailService(request); return flowDetailService.detail(); } + + + /** + * 流程节点记录 + * @param request 流程节点记录请求 + * @return 流程节点记录列表 + */ + public List processNodes(FlowProcessNodeRequest request) { + FlowProcessNodeService flowProcessNodeService = new FlowProcessNodeService(request); + return flowProcessNodeService.processNodes(); + } + /** * 创建流程 * diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java index 63290255..f0b79a8b 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowCreateService.java @@ -73,6 +73,13 @@ public long create() { List flowRecords = currentNode.generateCurrentRecords(session); + // 如果存在父流程,则同步更新到该子流程上 + if (this.request.isSubProcess()) { + flowRecords.forEach(flowRecord -> { + flowRecord.setParentId(this.request.getParentRecordId()); + }); + } + currentNode.verifySession(session); if (flowRecords.size() > 1) { diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowDetailService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowDetailService.java index 9b5e5691..639482e4 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowDetailService.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowDetailService.java @@ -1,64 +1,62 @@ package com.codingapi.flow.service.impl; -import com.codingapi.flow.action.IFlowAction; -import com.codingapi.flow.action.actions.PassAction; import com.codingapi.flow.backup.WorkflowBackup; import com.codingapi.flow.context.RepositoryHolderContext; import com.codingapi.flow.exception.FlowNotFoundException; -import com.codingapi.flow.form.FormData; -import com.codingapi.flow.manager.ActionManager; import com.codingapi.flow.node.IFlowNode; import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.pojo.request.FlowDetailRequest; import com.codingapi.flow.pojo.response.FlowContent; import com.codingapi.flow.record.FlowRecord; import com.codingapi.flow.record.FlowTodoMerge; import com.codingapi.flow.record.FlowTodoRecord; import com.codingapi.flow.repository.*; -import com.codingapi.flow.session.FlowSession; import com.codingapi.flow.workflow.Workflow; -import java.util.ArrayList; import java.util.List; -import java.util.Map; +/** + * 流程详情服务 + */ public class FlowDetailService { - private final String id; + private final FlowDetailRequest request; private final IFlowOperator currentOperator; private final FlowRecordRepository flowRecordRepository; private final WorkflowRepository workflowRepository; private final WorkflowBackupRepository workflowBackupRepository; - public FlowDetailService(String id, IFlowOperator currentOperator) { - this.id = id; - this.currentOperator = currentOperator; + public FlowDetailService(FlowDetailRequest request) { + this.request = request; + this.currentOperator = RepositoryHolderContext.getInstance().getOperatorById(request.getOperatorId()); this.flowRecordRepository = RepositoryHolderContext.getInstance().getFlowRecordRepository(); this.workflowRepository = RepositoryHolderContext.getInstance().getWorkflowRepository(); this.workflowBackupRepository = RepositoryHolderContext.getInstance().getWorkflowBackupRepository(); } - - private boolean isCreateWorkflow() { - return !id.matches("^[0-9]+$"); - } - public FlowContent detail() { - if (this.isCreateWorkflow()) { - Workflow workflow = workflowRepository.get(id); + if (this.request.isCreateWorkflow()) { + Workflow workflow = workflowRepository.get(this.request.getId()); if (workflow == null) { - throw FlowNotFoundException.workflow(id); + throw FlowNotFoundException.workflow(this.request.getId()); } return new FlowContentFactory(workflow, null, currentOperator).create(); } else { - FlowRecord flowRecord = flowRecordRepository.get(Long.parseLong(id)); + FlowRecord flowRecord = flowRecordRepository.get(Long.parseLong(this.request.getId())); if (flowRecord == null) { - throw FlowNotFoundException.record(Long.parseLong(id)); + throw FlowNotFoundException.record(Long.parseLong(this.request.getId())); } WorkflowBackup workflowBackup = workflowBackupRepository.get(flowRecord.getWorkBackupId()); if (workflowBackup == null) { throw FlowNotFoundException.workflow(flowRecord.getWorkBackupId() + " not found"); } Workflow workflow = workflowBackup.toWorkflow(); + + if(!flowRecord.isReadable()){ + flowRecord.read(); + RepositoryHolderContext.getInstance().saveRecord(flowRecord); + } + return new FlowContentFactory(workflow, flowRecord,currentOperator).create(); } } @@ -114,29 +112,13 @@ private void loadHistoryRecords() { } } - private void loadNextNodes() { - List nextNodes = new ArrayList<>(); + private void loadCurrentNode(){ IFlowNode currentNode = null; - FlowSession flowSession = null; - if (flowRecord != null) { + if(flowRecord!=null){ currentNode = workflow.getFlowNode(flowRecord.getNodeId()); - FormData formData = new FormData(workflow.getForm()); - formData.reset(flowRecord.getFormData()); - flowSession = flowRecord.createFlowSession(workflow,currentOperator,formData,flowRecord.toAdvice(workflow)); }else { currentNode = workflow.getStartNode(); - ActionManager actionManager = currentNode.actionManager(); - IFlowAction flowAction = actionManager.getAction(PassAction.class); - flowSession = FlowSession.startSession(currentOperator, workflow, currentNode, flowAction, null, 0); - } - if (currentNode != null) { - List nodes = workflow.nextNodes(currentNode); - if(nodes!=null && !nodes.isEmpty()){ - nextNodes.addAll(nodes); - } } - - this.flowContent.pushNextNodes(flowSession,nextNodes); if(currentNode!=null) { this.flowContent.pushCurrentNode(currentNode); } @@ -146,10 +128,9 @@ private void loadNextNodes() { public FlowContent create() { this.loadCurrentOperator(); this.loadWorkflow(); - this.loadNextNodes(); + this.loadCurrentNode(); this.loadTodoFlowRecords(); this.loadHistoryRecords(); - return flowContent; } } diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java new file mode 100644 index 00000000..61dd930a --- /dev/null +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/service/impl/FlowProcessNodeService.java @@ -0,0 +1,132 @@ +package com.codingapi.flow.service.impl; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.actions.PassAction; +import com.codingapi.flow.backup.WorkflowBackup; +import com.codingapi.flow.context.RepositoryHolderContext; +import com.codingapi.flow.exception.FlowNotFoundException; +import com.codingapi.flow.form.FormData; +import com.codingapi.flow.manager.ActionManager; +import com.codingapi.flow.manager.NodeStrategyManager; +import com.codingapi.flow.manager.OperatorManager; +import com.codingapi.flow.node.IFlowNode; +import com.codingapi.flow.node.nodes.StartNode; +import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.pojo.request.FlowProcessNodeRequest; +import com.codingapi.flow.pojo.response.ProcessNode; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.repository.FlowRecordRepository; +import com.codingapi.flow.repository.WorkflowBackupRepository; +import com.codingapi.flow.repository.WorkflowRepository; +import com.codingapi.flow.session.FlowAdvice; +import com.codingapi.flow.session.FlowSession; +import com.codingapi.flow.workflow.Workflow; + +import java.util.ArrayList; +import java.util.List; + +/** + * 流程节点记录服务 + */ +public class FlowProcessNodeService { + + private final FlowProcessNodeRequest request; + private final IFlowOperator currentOperator; + private final FlowRecordRepository flowRecordRepository; + private final WorkflowRepository workflowRepository; + private final WorkflowBackupRepository workflowBackupRepository; + + // 当前的流程记录,当id为workId时flowRecord为空 + private FlowRecord flowRecord; + // 当前的流程设计器 + private Workflow workflow; + // 当前的节点 + private IFlowNode currentNode; + // 流程节点记录 + private final List nodeList; + + public FlowProcessNodeService(FlowProcessNodeRequest request) { + this.request = request; + this.currentOperator = RepositoryHolderContext.getInstance().getOperatorById(request.getOperatorId()); + this.flowRecordRepository = RepositoryHolderContext.getInstance().getFlowRecordRepository(); + this.workflowRepository = RepositoryHolderContext.getInstance().getWorkflowRepository(); + this.workflowBackupRepository = RepositoryHolderContext.getInstance().getWorkflowBackupRepository(); + this.nodeList = new ArrayList<>(); + this.loadWorkflow(); + } + + private void loadWorkflow() { + String id = this.request.getId(); + if (this.request.isCreateWorkflow()) { + this.workflow = workflowRepository.get(id); + this.currentNode = this.workflow.getStartNode(); + } else { + FlowRecord flowRecord = flowRecordRepository.get(Long.parseLong(id)); + if (flowRecord == null) { + throw FlowNotFoundException.record(Long.parseLong(id)); + } + this.flowRecord = flowRecord; + WorkflowBackup workflowBackup = workflowBackupRepository.get(flowRecord.getWorkBackupId()); + if (workflowBackup == null) { + throw FlowNotFoundException.workflow(flowRecord.getWorkBackupId() + " not found"); + } + this.workflow = workflowBackup.toWorkflow(); + this.currentNode = this.workflow.getFlowNode(flowRecord.getNodeId()); + } + } + + public List processNodes() { + long backupId = 0; + if (this.flowRecord != null) { + backupId = this.flowRecord.getWorkBackupId(); + // 查询历史记录 + List historyRecords = flowRecordRepository.findBeforeRecords(flowRecord.getProcessId(), flowRecord.getId()); + for (FlowRecord historyRecord : historyRecords) { + ProcessNode processNode = new ProcessNode(historyRecord, this.workflow); + nodeList.add(processNode); + } + } + + ActionManager actionManager = currentNode.actionManager(); + IFlowAction flowAction = actionManager.getAction(PassAction.class); + FormData formData = new FormData(this.workflow.getForm()); + formData.reset(this.request.getFormData()); + + FlowSession flowSession = new FlowSession( + this.currentOperator, + this.workflow, + this.currentNode, + flowAction, + formData, + this.flowRecord, + new ArrayList<>(), + backupId, + FlowAdvice.nullFlowAdvice() + ); + + this.fetchNextNode(flowSession, List.of(this.currentNode)); + + // 推理后续 + return nodeList; + } + + + private void fetchNextNode(FlowSession flowSession, List nexNodes) { + for (IFlowNode flowNode : nexNodes) { + List operators = null; + if(flowNode.getType().equals(StartNode.NODE_TYPE)){ + operators = List.of(flowSession.getCurrentOperator()); + }else { + NodeStrategyManager nodeStrategyManager = flowNode.strategyManager(); + OperatorManager operatorManager = nodeStrategyManager.loadOperators(flowSession); + operators = operatorManager.getOperators(); + } + ProcessNode processNode = new ProcessNode(flowNode,operators); + this.nodeList.add(processNode); + List nextNodes = workflow.nextNodes(flowNode); + this.fetchNextNode(flowSession.updateSession(flowNode), nextNodes); + } + } + + +} diff --git a/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java b/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java index d59a4453..de9ac7f8 100644 --- a/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java +++ b/flow-engine-framework/src/main/java/com/codingapi/flow/session/FlowSession.java @@ -177,6 +177,10 @@ public String getCurrentNodeType() { return currentNode.getType(); } + public String getCurrentNodeName() { + return currentNode.getName(); + } + /** * 获取下一节点列表 * diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowDetailServiceTest.java b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowDetailServiceTest.java new file mode 100644 index 00000000..62aab23e --- /dev/null +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowDetailServiceTest.java @@ -0,0 +1,284 @@ +package com.codingapi.flow.service; + +import com.codingapi.flow.action.IFlowAction; +import com.codingapi.flow.action.actions.CustomAction; +import com.codingapi.flow.builder.ActionBuilder; +import com.codingapi.flow.builder.FormFieldPermissionsBuilder; +import com.codingapi.flow.builder.NodeStrategyBuilder; +import com.codingapi.flow.context.GatewayContext; +import com.codingapi.flow.form.FormMeta; +import com.codingapi.flow.form.FormMetaBuilder; +import com.codingapi.flow.form.permission.PermissionType; +import com.codingapi.flow.gateway.impl.UserGateway; +import com.codingapi.flow.node.nodes.ApprovalNode; +import com.codingapi.flow.node.nodes.EndNode; +import com.codingapi.flow.node.nodes.StartNode; +import com.codingapi.flow.pojo.body.FlowAdviceBody; +import com.codingapi.flow.pojo.request.FlowActionRequest; +import com.codingapi.flow.pojo.request.FlowCreateRequest; +import com.codingapi.flow.pojo.request.FlowDetailRequest; +import com.codingapi.flow.pojo.request.FlowProcessNodeRequest; +import com.codingapi.flow.pojo.response.FlowContent; +import com.codingapi.flow.pojo.response.ProcessNode; +import com.codingapi.flow.record.FlowRecord; +import com.codingapi.flow.repository.*; +import com.codingapi.flow.strategy.node.FormFieldPermissionStrategy; +import com.codingapi.flow.strategy.node.OperatorLoadStrategy; +import com.codingapi.flow.user.User; +import com.codingapi.flow.workflow.Workflow; +import com.codingapi.flow.workflow.WorkflowBuilder; +import org.junit.jupiter.api.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; + +public class FlowDetailServiceTest { + + private final FlowTodoRecordRepositoryImpl flowTodoRecordRepository = new FlowTodoRecordRepositoryImpl(); + private final FlowTodoMergeRepositoryImpl flowTodoMergeRepository = new FlowTodoMergeRepositoryImpl(); + private final FlowRecordRepositoryImpl flowRecordRepository = new FlowRecordRepositoryImpl(); + private final UserGateway userGateway = new UserGateway(); + private final WorkflowBackupRepository workflowBackupRepository = new WorkflowBackupRepositoryImpl(); + private final WorkflowRepository workflowRepository = new WorkflowRepositoryImpl(); + private final ParallelBranchRepository parallelBranchRepository = new ParallelBranchRepositoryImpl(); + private final DelayTaskRepository delayTaskRepository = new DelayTaskRepositoryImpl(); + private final UrgeIntervalRepository urgeIntervalRepository = new UrgeIntervalRepositoryImpl(); + private final FlowService flowService = new FlowService(workflowRepository, userGateway, flowRecordRepository, flowTodoRecordRepository, flowTodoMergeRepository, workflowBackupRepository, parallelBranchRepository, delayTaskRepository, urgeIntervalRepository); + + + /** + * 流程详情 + */ + @Test + void detail() { + + User user = new User(1, "user"); + User boss = new User(2, "boss"); + userGateway.save(user); + userGateway.save(boss); + + GatewayContext.getInstance().setFlowOperatorGateway(userGateway); + + FormMeta form = FormMetaBuilder.builder() + .name("请假流程") + .code("leave") + .addField("请假人", "name", "string") + .addField("请假天数", "days", "int") + .addField("请假事由", "reason", "string") + .build(); + + StartNode startNode = StartNode + .builder() + .strategies(NodeStrategyBuilder.builder() + .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build())) + .build()) + .actions(ActionBuilder.builder() + .addAction(new CustomAction()) + .build()) + .build(); + + ApprovalNode bossNode = ApprovalNode.builder() + .name("经理审批") + .strategies(NodeStrategyBuilder.builder() + .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build())) + .addStrategy(new OperatorLoadStrategy("def run(request){return [$bind.getOperatorById(2)]}")) + .build() + ) + .build(); + + EndNode endNode = EndNode.builder().build(); + Workflow workflow = WorkflowBuilder.builder() + .title("请假流程") + .code("leave") + .createdOperator(user) + .form(form) + .addNode(startNode) + .addNode(bossNode) + .addNode(endNode) + .build(); + + workflowRepository.save(workflow); + + FlowContent detail = flowService.detail(new FlowDetailRequest(workflow.getId(), user.getUserId())); + assertEquals(detail.getForm().getCode(), form.getCode()); + assertEquals(detail.getActions().size(), startNode.actionManager().getActions().size()); + assertNull(detail.getTodos()); + assertNull(detail.getHistories()); + + + Map data = Map.of("name", "lorne", "days", 1, "reason", "leave"); + + List startActions = startNode.actionManager().getActions(); + FlowCreateRequest userCreateRequest = new FlowCreateRequest(); + userCreateRequest.setWorkId(workflow.getId()); + userCreateRequest.setFormData(data); + userCreateRequest.setActionId(startActions.get(0).id()); + userCreateRequest.setOperatorId(user.getUserId()); + flowService.create(userCreateRequest); + + List userRecordList = flowRecordRepository.findTodoByOperator(user.getUserId()); + assertEquals(1, userRecordList.size()); + + detail = flowService.detail(new FlowDetailRequest(userRecordList.get(0).getId(), user.getUserId())); + assertEquals(detail.getForm().getCode(), form.getCode()); + assertEquals(detail.getActions().size(), startNode.actionManager().getActions().size()); + assertEquals(1, detail.getTodos().size()); + assertEquals(0, detail.getHistories().size()); + assertEquals(data, detail.getTodos().get(0).getData()); + + + FlowActionRequest userRequest = new FlowActionRequest(); + userRequest.setFormData(data); + userRequest.setRecordId(userRecordList.get(0).getId()); + userRequest.setAdvice(new FlowAdviceBody(startActions.get(0).id(), "同意", user.getUserId())); + flowService.action(userRequest); + + List bossRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); + assertEquals(1, bossRecordList.size()); + + detail = flowService.detail(new FlowDetailRequest(bossRecordList.get(0).getId(),boss.getUserId())); + assertEquals(detail.getForm().getCode(), form.getCode()); + assertEquals(detail.getActions().size(), bossNode.actionManager().getActions().size()); + assertEquals(1, detail.getTodos().size()); + assertEquals(1, detail.getHistories().size()); + assertEquals(data, detail.getTodos().get(0).getData()); + + List bossActions = bossNode.actionManager().getActions(); + + FlowActionRequest bossRequest = new FlowActionRequest(); + bossRequest.setFormData(data); + bossRequest.setRecordId(bossRecordList.get(0).getId()); + bossRequest.setAdvice(new FlowAdviceBody(bossActions.get(0).id(), "同意", boss.getUserId())); + flowService.action(bossRequest); + + List records = flowRecordRepository.findProcessRecords(bossRecordList.get(0).getProcessId()); + assertEquals(3, records.size()); + assertEquals(3, records.stream().filter(FlowRecord::isFinish).toList().size()); + + } + + + + + /** + * 流程详情 + */ + @Test + void processNodes() { + + User user = new User(1, "user"); + User boss = new User(2, "boss"); + userGateway.save(user); + userGateway.save(boss); + + GatewayContext.getInstance().setFlowOperatorGateway(userGateway); + + FormMeta form = FormMetaBuilder.builder() + .name("请假流程") + .code("leave") + .addField("请假人", "name", "string") + .addField("请假天数", "days", "int") + .addField("请假事由", "reason", "string") + .build(); + + StartNode startNode = StartNode + .builder() + .strategies(NodeStrategyBuilder.builder() + .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build())) + .build()) + .actions(ActionBuilder.builder() + .addAction(new CustomAction()) + .build()) + .build(); + + ApprovalNode bossNode = ApprovalNode.builder() + .name("经理审批") + .strategies(NodeStrategyBuilder.builder() + .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() + .addPermission("leave", "name", PermissionType.WRITE) + .addPermission("leave", "days", PermissionType.WRITE) + .addPermission("leave", "reason", PermissionType.WRITE) + .build())) + .addStrategy(new OperatorLoadStrategy("def run(request){return [$bind.getOperatorById(2)]}")) + .build() + ) + .build(); + + EndNode endNode = EndNode.builder().build(); + Workflow workflow = WorkflowBuilder.builder() + .title("请假流程") + .code("leave") + .createdOperator(user) + .form(form) + .addNode(startNode) + .addNode(bossNode) + .addNode(endNode) + .build(); + + workflowRepository.save(workflow); + + Map data = Map.of("name", "lorne", "days", 1, "reason", "leave"); + + List nodeList = flowService.processNodes(new FlowProcessNodeRequest(workflow.getId(), user.getUserId(),data)); + assertEquals(3,nodeList.size()); + assertEquals(0,nodeList.stream().filter(ProcessNode::isHistory).toList().size()); + + List startActions = startNode.actionManager().getActions(); + FlowCreateRequest userCreateRequest = new FlowCreateRequest(); + userCreateRequest.setWorkId(workflow.getId()); + userCreateRequest.setFormData(data); + userCreateRequest.setActionId(startActions.get(0).id()); + userCreateRequest.setOperatorId(user.getUserId()); + flowService.create(userCreateRequest); + + List userRecordList = flowRecordRepository.findTodoByOperator(user.getUserId()); + assertEquals(1, userRecordList.size()); + + nodeList = flowService.processNodes(new FlowProcessNodeRequest(userRecordList.get(0).getId(), user.getUserId(),data)); + assertEquals(3,nodeList.size()); + assertEquals(0,nodeList.stream().filter(ProcessNode::isHistory).toList().size()); + + + FlowActionRequest userRequest = new FlowActionRequest(); + userRequest.setFormData(data); + userRequest.setRecordId(userRecordList.get(0).getId()); + userRequest.setAdvice(new FlowAdviceBody(startActions.get(0).id(), "同意", user.getUserId())); + flowService.action(userRequest); + + List bossRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); + assertEquals(1, bossRecordList.size()); + + + nodeList = flowService.processNodes(new FlowProcessNodeRequest(bossRecordList.get(0).getId(), boss.getUserId(),data)); + assertEquals(3,nodeList.size()); + assertEquals(1,nodeList.stream().filter(ProcessNode::isHistory).toList().size()); + + List bossActions = bossNode.actionManager().getActions(); + + FlowActionRequest bossRequest = new FlowActionRequest(); + bossRequest.setFormData(data); + bossRequest.setRecordId(bossRecordList.get(0).getId()); + bossRequest.setAdvice(new FlowAdviceBody(bossActions.get(0).id(), "同意", boss.getUserId())); + flowService.action(bossRequest); + + List records = flowRecordRepository.findProcessRecords(bossRecordList.get(0).getProcessId()); + assertEquals(3, records.size()); + assertEquals(3, records.stream().filter(FlowRecord::isFinish).toList().size()); + + } +} diff --git a/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowMergeableServiceTest.java b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowMergeableServiceTest.java index 25c402cf..843205ad 100644 --- a/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowMergeableServiceTest.java +++ b/flow-engine-framework/src/test/java/com/codingapi/flow/service/FlowMergeableServiceTest.java @@ -16,10 +16,9 @@ import com.codingapi.flow.pojo.body.FlowAdviceBody; import com.codingapi.flow.pojo.request.FlowActionRequest; import com.codingapi.flow.pojo.request.FlowCreateRequest; -import com.codingapi.flow.pojo.response.FlowContent; import com.codingapi.flow.record.FlowRecord; -import com.codingapi.flow.record.FlowTodoRecord; import com.codingapi.flow.record.FlowTodoMerge; +import com.codingapi.flow.record.FlowTodoRecord; import com.codingapi.flow.repository.*; import com.codingapi.flow.strategy.node.FormFieldPermissionStrategy; import com.codingapi.flow.strategy.node.OperatorLoadStrategy; @@ -35,7 +34,6 @@ import java.util.Set; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; public class FlowMergeableServiceTest { @@ -174,135 +172,6 @@ void mergeableRecords() { List todoMargeList = flowTodoMergeRepository.findAll(); assertEquals(0, todoMargeList.size()); - - } - - - /** - * 流程详情 - */ - @Test - void detail() { - - User user = new User(1, "user"); - User boss = new User(2, "boss"); - userGateway.save(user); - userGateway.save(boss); - - GatewayContext.getInstance().setFlowOperatorGateway(userGateway); - - FormMeta form = FormMetaBuilder.builder() - .name("请假流程") - .code("leave") - .addField("请假人", "name", "string") - .addField("请假天数", "days", "int") - .addField("请假事由", "reason", "string") - .build(); - - StartNode startNode = StartNode - .builder() - .strategies(NodeStrategyBuilder.builder() - .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build())) - .build()) - .actions(ActionBuilder.builder() - .addAction(new CustomAction()) - .build()) - .build(); - - ApprovalNode bossNode = ApprovalNode.builder() - .name("经理审批") - .strategies(NodeStrategyBuilder.builder() - .addStrategy(new FormFieldPermissionStrategy(FormFieldPermissionsBuilder.builder() - .addPermission("leave", "name", PermissionType.WRITE) - .addPermission("leave", "days", PermissionType.WRITE) - .addPermission("leave", "reason", PermissionType.WRITE) - .build())) - .addStrategy(new OperatorLoadStrategy("def run(request){return [$bind.getOperatorById(2)]}")) - .build() - ) - .build(); - - EndNode endNode = EndNode.builder().build(); - Workflow workflow = WorkflowBuilder.builder() - .title("请假流程") - .code("leave") - .createdOperator(user) - .form(form) - .addNode(startNode) - .addNode(bossNode) - .addNode(endNode) - .build(); - - workflowRepository.save(workflow); - - FlowContent detail = flowService.detail(workflow.getId(), user); - assertEquals(detail.getForm().getCode(), form.getCode()); - assertEquals(detail.getActions().size(), startNode.actionManager().getActions().size()); - assertEquals(1, detail.getNextNodes().size()); - assertNull(detail.getTodos()); - assertNull(detail.getHistories()); - - - Map data = Map.of("name", "lorne", "days", 1, "reason", "leave"); - - List startActions = startNode.actionManager().getActions(); - FlowCreateRequest userCreateRequest = new FlowCreateRequest(); - userCreateRequest.setWorkId(workflow.getId()); - userCreateRequest.setFormData(data); - userCreateRequest.setActionId(startActions.get(0).id()); - userCreateRequest.setOperatorId(user.getUserId()); - flowService.create(userCreateRequest); - - List userRecordList = flowRecordRepository.findTodoByOperator(user.getUserId()); - assertEquals(1, userRecordList.size()); - - detail = flowService.detail(String.valueOf(userRecordList.get(0).getId()), user); - assertEquals(detail.getForm().getCode(), form.getCode()); - assertEquals(detail.getActions().size(), startNode.actionManager().getActions().size()); - assertEquals(1, detail.getNextNodes().size()); - assertEquals(1, detail.getTodos().size()); - assertEquals(1, detail.getNextNodes().get(0).getOperators().size()); - assertEquals(boss.getUserId(), detail.getNextNodes().get(0).getOperators().get(0).getId()); - assertEquals(0, detail.getHistories().size()); - assertEquals(data, detail.getTodos().get(0).getData()); - - - FlowActionRequest userRequest = new FlowActionRequest(); - userRequest.setFormData(data); - userRequest.setRecordId(userRecordList.get(0).getId()); - userRequest.setAdvice(new FlowAdviceBody(startActions.get(0).id(), "同意", user.getUserId())); - flowService.action(userRequest); - - List bossRecordList = flowRecordRepository.findTodoByOperator(boss.getUserId()); - assertEquals(1, bossRecordList.size()); - - detail = flowService.detail(String.valueOf(bossRecordList.get(0).getId()), user); - assertEquals(detail.getForm().getCode(), form.getCode()); - assertEquals(detail.getActions().size(), bossNode.actionManager().getActions().size()); - assertEquals(1, detail.getNextNodes().size()); - assertEquals(1, detail.getTodos().size()); - assertEquals(1, detail.getHistories().size()); - assertEquals(0, detail.getNextNodes().get(0).getOperators().size()); - assertEquals(endNode.getId(), detail.getNextNodes().get(0).getNodeId()); - assertEquals(data, detail.getTodos().get(0).getData()); - - List bossActions = bossNode.actionManager().getActions(); - - FlowActionRequest bossRequest = new FlowActionRequest(); - bossRequest.setFormData(data); - bossRequest.setRecordId(bossRecordList.get(0).getId()); - bossRequest.setAdvice(new FlowAdviceBody(bossActions.get(0).id(), "同意", boss.getUserId())); - flowService.action(bossRequest); - - List records = flowRecordRepository.findProcessRecords(bossRecordList.get(0).getProcessId()); - assertEquals(3, records.size()); - assertEquals(3, records.stream().filter(FlowRecord::isFinish).toList().size()); - - } } diff --git a/flow-engine-starter-api/src/main/java/com/codingapi/flow/api/controller/FlowRecordController.java b/flow-engine-starter-api/src/main/java/com/codingapi/flow/api/controller/FlowRecordController.java index 68cc0960..5c683161 100644 --- a/flow-engine-starter-api/src/main/java/com/codingapi/flow/api/controller/FlowRecordController.java +++ b/flow-engine-starter-api/src/main/java/com/codingapi/flow/api/controller/FlowRecordController.java @@ -1,15 +1,20 @@ package com.codingapi.flow.api.controller; import com.codingapi.flow.operator.IFlowOperator; +import com.codingapi.flow.pojo.request.FlowActionRequest; +import com.codingapi.flow.pojo.request.FlowCreateRequest; +import com.codingapi.flow.pojo.request.FlowDetailRequest; +import com.codingapi.flow.pojo.request.FlowProcessNodeRequest; +import com.codingapi.flow.pojo.response.FlowContent; +import com.codingapi.flow.pojo.response.ProcessNode; import com.codingapi.flow.service.FlowService; import com.codingapi.springboot.framework.dto.request.IdRequest; +import com.codingapi.springboot.framework.dto.response.MultiResponse; import com.codingapi.springboot.framework.dto.response.Response; import com.codingapi.springboot.framework.dto.response.SingleResponse; import com.codingapi.springboot.framework.user.UserContext; import lombok.AllArgsConstructor; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/api/cmd/record") @@ -19,10 +24,34 @@ public class FlowRecordController { private final FlowService flowService; @GetMapping("/detail") - public Response detail(IdRequest request) { - String id = request.getStringId(); + public SingleResponse detail(IdRequest idRequest) { IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); - return SingleResponse.of(flowService.detail(id, current)); + return SingleResponse.of(flowService.detail(new FlowDetailRequest(idRequest.getStringId(), current.getUserId()))); + } + + + @PostMapping("/processNodes") + public MultiResponse processNodes(@RequestBody FlowProcessNodeRequest request) { + IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); + request.setOperatorId(current.getUserId()); + return MultiResponse.of(flowService.processNodes(request)); + } + + + @PostMapping("/create") + public SingleResponse create(@RequestBody FlowCreateRequest request) { + IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); + request.setOperatorId(current.getUserId()); + return SingleResponse.of(flowService.create(request)); + } + + + @PostMapping("/action") + public Response action(@RequestBody FlowActionRequest request) { + IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); + request.updateOperatorId(current.getUserId()); + flowService.action(request); + return Response.buildSuccess(); } } diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowRecordConvertor.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowRecordConvertor.java index dd4f31e1..81cf33dd 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowRecordConvertor.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowRecordConvertor.java @@ -17,7 +17,9 @@ public static FlowRecord convert(FlowRecordEntity entity) { entity.getWorkCode(), entity.getNodeId(), entity.getNodeType(), + entity.getNodeName(), entity.getFromId(), + entity.getParentId(), mapConvertor.convertToEntityAttribute(entity.getFormData()), entity.getTitle(), entity.getReadTime(), @@ -59,12 +61,16 @@ public static FlowRecordEntity convert(FlowRecord record) { return null; } FlowRecordEntity entity = new FlowRecordEntity(); - entity.setId(record.getId()); + if(record.getId()>0) { + entity.setId(record.getId()); + } entity.setWorkBackupId(record.getWorkBackupId()); entity.setWorkCode(record.getWorkCode()); entity.setNodeId(record.getNodeId()); entity.setNodeType(record.getNodeType()); + entity.setNodeName(record.getNodeName()); entity.setFromId(record.getFromId()); + entity.setParentId(record.getParentId()); entity.setFormData(mapConvertor.convertToDatabaseColumn(record.getFormData())); entity.setTitle(record.getTitle()); entity.setReadTime(record.getReadTime()); diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoMargeConvertor.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoMargeConvertor.java index c03dcd32..cf06c842 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoMargeConvertor.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoMargeConvertor.java @@ -22,7 +22,9 @@ public static FlowTodoMargeEntity convert(FlowTodoMerge marge) { return null; } FlowTodoMargeEntity entity = new FlowTodoMargeEntity(); - entity.setId(marge.getId()); + if(marge.getId()>0) { + entity.setId(marge.getId()); + } entity.setTodoId(marge.getTodoId()); entity.setRecordId(marge.getRecordId()); entity.setCreateTime(marge.getCreateTime()); diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoRecordConvertor.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoRecordConvertor.java index 99aea3fb..7f41a8dd 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoRecordConvertor.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/FlowTodoRecordConvertor.java @@ -16,6 +16,7 @@ public static FlowTodoRecord convert(FlowTodoRecordEntity entity){ entity.getWorkCode(), entity.getNodeId(), entity.getNodeType(), + entity.getNodeName(), entity.getTitle(), entity.getReadTime(), entity.getCurrentOperatorId(), @@ -38,12 +39,15 @@ public static FlowTodoRecordEntity convert(FlowTodoRecord record){ } FlowTodoRecordEntity entity = new FlowTodoRecordEntity(); - entity.setId(record.getId()); + if(record.getId()>0) { + entity.setId(record.getId()); + } entity.setProcessId(record.getProcessId()); entity.setWorkBackupId(record.getWorkBackupId()); entity.setWorkCode(record.getWorkCode()); entity.setNodeId(record.getNodeId()); entity.setNodeType(record.getNodeType()); + entity.setNodeName(record.getNodeName()); entity.setTitle(record.getTitle()); entity.setReadTime(record.getReadTime()); entity.setCurrentOperatorId(record.getCurrentOperatorId()); diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/UrgeIntervalConvertor.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/UrgeIntervalConvertor.java index 06efc6a5..01918916 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/UrgeIntervalConvertor.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/UrgeIntervalConvertor.java @@ -10,7 +10,9 @@ public static UrgeIntervalEntity convert(UrgeInterval interval) { return null; } UrgeIntervalEntity entity = new UrgeIntervalEntity(); - entity.setId(interval.getId()); + if(interval.getId()>0) { + entity.setId(interval.getId()); + } entity.setProcessId(interval.getProcessId()); entity.setRecordId(interval.getRecordId()); entity.setCreateTime(interval.getCreateTime()); diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/WorkflowBackupConvertor.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/WorkflowBackupConvertor.java index 7ae1cce6..6660cce3 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/WorkflowBackupConvertor.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/convert/WorkflowBackupConvertor.java @@ -5,25 +5,27 @@ public class WorkflowBackupConvertor { - public static WorkflowBackupEntity convert(WorkflowBackup workflowBackup){ - if(workflowBackup==null){ + public static WorkflowBackupEntity convert(WorkflowBackup workflowBackup) { + if (workflowBackup == null) { return null; } WorkflowBackupEntity entity = new WorkflowBackupEntity(); + if (workflowBackup.getId() > 0) { + entity.setId(workflowBackup.getId()); + } entity.setWorkflow(workflowBackup.getWorkflow()); entity.setWorkId(workflowBackup.getWorkId()); entity.setWorkCode(workflowBackup.getWorkCode()); entity.setWorkVersion(workflowBackup.getWorkVersion()); entity.setWorkTitle(workflowBackup.getWorkTitle()); entity.setCreateTime(workflowBackup.getCreateTime()); - entity.setId(workflowBackup.getId()); return entity; } - public static WorkflowBackup convert(WorkflowBackupEntity entity){ - if(entity==null){ + public static WorkflowBackup convert(WorkflowBackupEntity entity) { + if (entity == null) { return null; } - return new WorkflowBackup(entity.getId(),entity.getWorkId(),entity.getWorkCode(),entity.getWorkVersion(),entity.getWorkTitle(),entity.getCreateTime(),entity.getWorkflow()); + return new WorkflowBackup(entity.getId(), entity.getWorkId(), entity.getWorkCode(), entity.getWorkVersion(), entity.getWorkTitle(), entity.getCreateTime(), entity.getWorkflow()); } } diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java index 5cc38d5a..c51807b9 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowRecordEntity.java @@ -30,9 +30,17 @@ public class FlowRecordEntity { */ private String nodeType; /** - * 父节点id + * 节点名称 + */ + private String nodeName; + /** + * 来源id */ private Long fromId; + /** + * 父节点id(用于子流程中) + */ + private Long parentId; /** * 表单数据 */ diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java index e0c50dea..c36ae754 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/entity/FlowTodoRecordEntity.java @@ -38,6 +38,10 @@ public class FlowTodoRecordEntity { * 节点类型 */ private String nodeType; + /** + * 节点名称 + */ + private String nodeName; /** * 消息标题 diff --git a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/pojo/FlowRecordContent.java b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/pojo/FlowRecordContent.java index ae40643e..5cb3dcab 100644 --- a/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/pojo/FlowRecordContent.java +++ b/flow-engine-starter-infra/src/main/java/com/codingapi/flow/infra/pojo/FlowRecordContent.java @@ -31,6 +31,11 @@ public class FlowRecordContent { */ private String nodeType; + /** + * 节点名称 + */ + private String nodeName; + /** * 消息标题 */ @@ -112,6 +117,7 @@ public static FlowRecordContent convert(FlowTodoRecordEntity todoRecord){ content.setWorkCode(todoRecord.getWorkCode()); content.setNodeId(todoRecord.getNodeId()); content.setNodeType(todoRecord.getNodeType()); + content.setNodeName(todoRecord.getNodeName()); content.setTitle(todoRecord.getTitle()); content.setReadTime(todoRecord.getReadTime()); content.setCurrentOperatorId(todoRecord.getCurrentOperatorId()); @@ -138,6 +144,7 @@ public static FlowRecordContent convert(FlowRecordEntity record){ content.setWorkCode(record.getWorkCode()); content.setNodeId(record.getNodeId()); content.setNodeType(record.getNodeType()); + content.setNodeName(record.getNodeName()); content.setTitle(record.getTitle()); content.setReadTime(record.getReadTime()); content.setCurrentOperatorId(record.getCurrentOperatorId()); diff --git a/flow-engine-starter-query/src/main/java/com/codingapi/flow/query/controller/FlowRecordQueryController.java b/flow-engine-starter-query/src/main/java/com/codingapi/flow/query/controller/FlowRecordQueryController.java index 976fd4ce..6378c0e0 100644 --- a/flow-engine-starter-query/src/main/java/com/codingapi/flow/query/controller/FlowRecordQueryController.java +++ b/flow-engine-starter-query/src/main/java/com/codingapi/flow/query/controller/FlowRecordQueryController.java @@ -12,6 +12,7 @@ import lombok.AllArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Sort; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -29,6 +30,7 @@ public class FlowRecordQueryController { */ @GetMapping("/list") public MultiResponse list(SearchRequest request) { + request.addSort(Sort.by("id").descending()); Page page = flowRecordEntityRepository.searchRequest(request); return MultiResponse.of(page.map(FlowRecordContent::convert)); } @@ -41,7 +43,7 @@ public MultiResponse list(SearchRequest request) { public MultiResponse todo(SearchRequest request) { IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); PageRequest pageRequest = request.toPageRequest(FlowRecordEntity.class); - Page page = flowTodoRecordEntityRepository.findTodoRecordPage(current.getUserId(),pageRequest); + Page page = flowTodoRecordEntityRepository.findTodoRecordPage(current.getUserId(),pageRequest.withSort(Sort.by("id").descending())); return MultiResponse.of(page.map(FlowRecordContent::convert)); } @@ -52,7 +54,7 @@ public MultiResponse todo(SearchRequest request) { public MultiResponse notify(SearchRequest request) { IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); PageRequest pageRequest = request.toPageRequest(FlowRecordEntity.class); - Page page =flowRecordEntityRepository.findNotifyRecordPage(current.getUserId(),pageRequest); + Page page =flowRecordEntityRepository.findNotifyRecordPage(current.getUserId(),pageRequest.withSort(Sort.by("id").descending())); return MultiResponse.of(page.map(FlowRecordContent::convert)); } @@ -64,7 +66,7 @@ public MultiResponse notify(SearchRequest request) { public MultiResponse done(SearchRequest request) { IFlowOperator current = (IFlowOperator) UserContext.getInstance().current(); PageRequest pageRequest = request.toPageRequest(FlowRecordEntity.class); - Page page =flowRecordEntityRepository.findDoneRecordPage(current.getUserId(),pageRequest); + Page page =flowRecordEntityRepository.findDoneRecordPage(current.getUserId(),pageRequest.withSort(Sort.by("id").descending())); return MultiResponse.of(page.map(FlowRecordContent::convert)); } } diff --git a/frontend/apps/app-pc/src/api/record.ts b/frontend/apps/app-pc/src/api/record.ts index 220b2d37..1af11fa6 100644 --- a/frontend/apps/app-pc/src/api/record.ts +++ b/frontend/apps/app-pc/src/api/record.ts @@ -10,4 +10,8 @@ export const todo = (request: any) => { export const done = (request: any) => { return httpClient.page('/api/query/record/done', request, {}, {}, []); +} + +export const notify = (request: any) => { + return httpClient.page('/api/query/record/notify', request, {}, {}, []); } \ No newline at end of file diff --git a/frontend/apps/app-pc/src/config/plugin-view.tsx b/frontend/apps/app-pc/src/config/plugin-view.tsx new file mode 100644 index 00000000..95b2fb0c --- /dev/null +++ b/frontend/apps/app-pc/src/config/plugin-view.tsx @@ -0,0 +1,4 @@ +import {ViewPlugin} from "@flow-engine/flow-design"; +import {LeaveView} from "@/views/leave.tsx"; + +ViewPlugin.getInstance().register('default',LeaveView); \ No newline at end of file diff --git a/frontend/apps/app-pc/src/index.tsx b/frontend/apps/app-pc/src/index.tsx index b3a719ca..67dd06b9 100644 --- a/frontend/apps/app-pc/src/index.tsx +++ b/frontend/apps/app-pc/src/index.tsx @@ -7,6 +7,7 @@ import zhCN from 'antd/locale/zh_CN'; import dayjs from 'dayjs'; import 'dayjs/locale/zh-cn'; import "./index.css"; +import "./config/plugin-view"; dayjs.locale('zh'); diff --git a/frontend/apps/app-pc/src/pages/todo.tsx b/frontend/apps/app-pc/src/pages/todo.tsx index 574fdedf..9188829d 100644 --- a/frontend/apps/app-pc/src/pages/todo.tsx +++ b/frontend/apps/app-pc/src/pages/todo.tsx @@ -1,5 +1,5 @@ import React from "react"; -import {done, list, todo} from "@/api/record.ts"; +import {done, list, notify, todo} from "@/api/record.ts"; import { type ActionType, ApprovalPanelDrawer, @@ -8,20 +8,24 @@ import { WorkflowSelectModal } from "@flow-engine/flow-design"; import {Button, Space, Tabs, type TabsProps} from "antd"; +import dayjs from "dayjs"; const TodoPage: React.FC = () => { const actionAll = React.useRef(null); const actionTodo = React.useRef(null); const actionDone = React.useRef(null); + const actionNotify = React.useRef(null); const [selectVisible, setSelectVisible] = React.useState(false); const [approvalVisible, setApprovalVisible] = React.useState(false); const [workflowCode, setWorkflowCode] = React.useState(''); + const [currentRecordId, setCurrentRecordId] = React.useState(''); + const [currentTab, setCurrentTab] = React.useState('todo'); const columns: TableProps['columns'] = [ { - dataIndex: 'id', + dataIndex: 'recordId', title: '编号', }, { @@ -31,94 +35,144 @@ const TodoPage: React.FC = () => { { dataIndex: 'readTime', title: '读取状态', + render: (value, record) => { + return value ? '已读' : '未读'; + } + }, + { + dataIndex: 'nodeName', + title: '节点名称', }, { dataIndex: 'createTime', title: '创建时间', + render: (text, record) => { + return dayjs(text).format('YYYY-MM-DD HH:mm:ss'); + } }, { dataIndex: 'currentOperatorId', title: '审批人', + hidden: true, + }, + { + dataIndex: 'currentOperatorName', + title: '审批人', }, { dataIndex: 'recordState', title: '状态', + render: (text, record) => { + return text ? '已办' : '待办'; + } }, { dataIndex: 'option', title: '操作', render: (value, record) => { - return ( - - { + if(currentTab==='todo'){ + return ( + + { + setCurrentRecordId(record.recordId); + setApprovalVisible(true); + }} + >办理 + + ) + } - }}>办理 - - ) } } ]; const items: TabsProps['items'] = [ { - key: 'all', - label: '全部流程', + key: 'todo', + label: '我的待办', children: ( { - return list(request); + return todo(request); }} /> ) }, { - key: 'todo', - label: '我的待办', + key: 'done', + label: '我的已办', children: (
{ - return todo(request); + return done(request); }} /> ) }, { - key: 'done', - label: '我的已办', + key: 'notify', + label: '我的抄送', children: (
{ - return done(request); + return notify(request); }} /> ) + }, + { + key: 'all', + label: '全部流程', + children: ( +
{ + return list(request); + }} + /> + ) + }, + ]; + + const reloadCurrentTab = () => { + if (currentTab === 'all') { + actionAll.current?.reload(); + } + if (currentTab === 'done') { + actionDone.current?.reload(); } - ] + if (currentTab === 'todo') { + actionTodo.current?.reload(); + } + if (currentTab === 'notify') { + actionNotify.current?.reload(); + } + } + + React.useEffect(() => { + reloadCurrentTab(); + },[currentTab]); return (
{ - if (currentKey === 'all') { - actionAll.current?.reload(); - } - if (currentKey === 'done') { - actionDone.current?.reload(); - } - if (currentKey === 'todo') { - actionTodo.current?.reload(); - } + setCurrentTab(currentKey); }} tabBarExtraContent={{ right: ( @@ -147,8 +201,10 @@ const TodoPage: React.FC = () => { { setApprovalVisible(false); + reloadCurrentTab(); }} />
diff --git a/frontend/apps/app-pc/src/views/leave.tsx b/frontend/apps/app-pc/src/views/leave.tsx new file mode 100644 index 00000000..46869216 --- /dev/null +++ b/frontend/apps/app-pc/src/views/leave.tsx @@ -0,0 +1,40 @@ +import React from "react"; +import {type ViewComponentProps} from "@flow-engine/flow-design"; +import {Form, Input} from "antd"; +import {ObjectUtils} from "@flow-engine/flow-core"; + +export const LeaveView: React.FC = (props) => { + + const [values, setValues] = React.useState({}); + + const form = props.form; + + return ( +
{ + const latestValues = form.getFieldsValue(); + if(ObjectUtils.isEqual(values,latestValues)){ + return; + } + setValues(latestValues); + props.onValuesChange?.(latestValues); + }} + > + + + + + + + + + ) +}; \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index ec2b9e6e..477b2991 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -8,6 +8,7 @@ "build:flow-types": "pnpm -F @flow-engine/flow-types build", "build:flow-engine": "pnpm -F @flow-engine/flow-design build", "build": "pnpm run build:flow-core && pnpm run build:flow-types && pnpm run build:flow-engine ", + "build:app-pc": "pnpm -F @flow-engine/app-pc build", "dev:app-pc": "pnpm -F @flow-engine/app-pc dev", "dev:app-mobile": "pnpm -F @flow-engine/app-mobile dev", "watch:flow-design": "pnpm -F @flow-engine/flow-design dev" diff --git a/frontend/packages/flow-core/src/object.ts b/frontend/packages/flow-core/src/object.ts index 9bca3838..7013adc9 100644 --- a/frontend/packages/flow-core/src/object.ts +++ b/frontend/packages/flow-core/src/object.ts @@ -4,6 +4,9 @@ export class ObjectUtils { return Object.keys(obj).length === 0 && obj.constructor === Object; } + public static isEqual(obj1: any, obj2: any): boolean { + return JSON.stringify(obj1) === JSON.stringify(obj2); + } public static cleanObject(obj: any, options = { removeNull: true, diff --git a/frontend/packages/flow-design/src/api/record.ts b/frontend/packages/flow-design/src/api/record.ts index 044b79c9..5504fa8a 100644 --- a/frontend/packages/flow-design/src/api/record.ts +++ b/frontend/packages/flow-design/src/api/record.ts @@ -4,3 +4,14 @@ export const detail = (id:string) => { return httpClient.get('/api/cmd/record/detail',{id}); } +export const processNodes = (body:any) => { + return httpClient.post('/api/cmd/record/processNodes',body); +} + +export const create = (body:any) => { + return httpClient.post('/api/cmd/record/create',body); +} + +export const action = (body:any) => { + return httpClient.post('/api/cmd/record/action',body); +} \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/context/index.ts b/frontend/packages/flow-design/src/components/flow-approval/context/index.ts index b2489df2..995c22d0 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/context/index.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/context/index.ts @@ -1,12 +1,14 @@ import React from "react"; import {ApprovalLayoutProps, State} from "../typings"; import {Presenter} from "../presenters"; +import {FormActionContext} from "@/components/flow-approval/presenters/form"; export class ApprovalContextScope { private readonly presenter: Presenter; private readonly props: ApprovalLayoutProps; + constructor(presenter: Presenter, props: ApprovalLayoutProps) { this.presenter = presenter; this.props = props; @@ -20,6 +22,7 @@ export class ApprovalContextScope { return this.presenter; } + public close() { this.props.onClose?.(); } diff --git a/frontend/packages/flow-design/src/components/flow-approval/index.tsx b/frontend/packages/flow-design/src/components/flow-approval/index.tsx index a128e4ca..0b709524 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/index.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/index.tsx @@ -4,7 +4,6 @@ import {detail} from "@/api/record"; import {FlowContent} from "@/components/flow-approval/typings"; import {ApprovalLayout} from "@/components/flow-approval/layout"; - interface ApprovalPanelProps { workflowCode?: string; recordId?:string; @@ -16,7 +15,7 @@ export const ApprovalPanel: React.FC = (props) => { const [content,dispatch] = React.useState(undefined); React.useEffect(()=>{ - const id = props.workflowCode || props.recordId || ''; + const id = props.recordId || props.workflowCode || ''; detail(id).then(res=>{ if(res.success){ dispatch(res.data); diff --git a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx index fe3950e4..a346673e 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/layout/body.tsx @@ -1,14 +1,89 @@ import React from "react"; import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; +import {Col, Form, Row} from "antd"; +import {ViewPlugin} from "@/plugins/view"; + +interface FormViewComponentProps{ + onValuesChange?:(values:any)=>void; +} + +const FormViewComponent: React.FC = (props) => { + const {state, context} = useApprovalContext(); + const ViewComponent = ViewPlugin.getInstance().get(state.flow?.view || 'default'); + // 是否可合并审批 + const mergeable = state.flow?.mergeable || false; + const todos = state.flow?.todos || []; + const viewForms = todos.length>0?todos.map(item => { + return { + instance: Form.useForm()[0], + data: item.data, + } + }):[ + { + instance: Form.useForm()[0], + data: undefined, + } + ] + + React.useEffect(() => { + viewForms.forEach(item => { + const formInstance = item.instance; + const data = item.data; + context.getPresenter().getFormActionContext().addAction({ + save(): any { + return formInstance.getFieldsValue(); + }, + key(): string { + return 'view-form' + } + }); + formInstance.setFieldsValue(data); + }); + }, []); + + if (ViewComponent) { + if (mergeable) { + return ( +
+

合并审批

+
+ ) + } + return ( + <> + {viewForms.map((item, index) => ( + + ))} + + ) + } +} export const Body = () => { - const {state,context} = useApprovalContext(); - const json = JSON.stringify(state); + const {state, context} = useApprovalContext(); + + const handleValuesChange = (values:any) => { + context.getPresenter().processNodes().then(nodes => { + console.log('流程节点:', nodes); + }); + } return ( -
- body: {json} -
+ +
+ 表单详情 + + + + 流转历史 + + ) } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/layout/header.tsx b/frontend/packages/flow-design/src/components/flow-approval/layout/header.tsx index 0b057619..8ab841ce 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/layout/header.tsx +++ b/frontend/packages/flow-design/src/components/flow-approval/layout/header.tsx @@ -1,15 +1,37 @@ import React from "react"; import {useApprovalContext} from "@/components/flow-approval/hooks/use-approval-context"; +import {Button, Flex, message} from "antd"; export const Header = () => { - const {state,context} = useApprovalContext(); + const {state, context} = useApprovalContext() + + const actions = state.flow?.actions || []; + + const actionPresenter = context.getPresenter().getFlowActionPresenter(); return ( -
- Header - -
+ + {actions.map(item => { + return ( + + ) + })} + + ) } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/model.ts b/frontend/packages/flow-design/src/components/flow-approval/model.ts index b4a269fd..a921c5ff 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/model.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/model.ts @@ -1,5 +1,24 @@ import {FlowApprovalApi} from "@/components/flow-approval/typings"; +import {action as actionRecord, create as createRecord, processNodes as postProcessNodes} from "@/api/record"; export class FlowApprovalApiImpl implements FlowApprovalApi { + create = async (body: Record)=> { + const response = await createRecord(body); + if(response.success){ + return response.data; + } + } + + action =async (body: Record)=> { + return await actionRecord(body); + } + + processNodes =async (body: Record)=> { + const response = await postProcessNodes(body); + if(response.success){ + return response.data; + } + } + } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/presenters/action.ts b/frontend/packages/flow-design/src/components/flow-approval/presenters/action.ts new file mode 100644 index 00000000..9709cea5 --- /dev/null +++ b/frontend/packages/flow-design/src/components/flow-approval/presenters/action.ts @@ -0,0 +1,63 @@ +import {FlowApprovalApi, State} from "@/components/flow-approval/typings"; +import {FormActionContext} from "@/components/flow-approval/presenters/form"; + +export class FlowActionPresenter { + + private readonly api: FlowApprovalApi; + private readonly formActionContext:FormActionContext; + private state: State; + + constructor(state: State, + api: FlowApprovalApi, + formActionContext:FormActionContext) { + this.state = state; + this.api = api; + this.formActionContext = formActionContext; + } + + public syncState(state: State) { + this.state = state; + } + + public async processNodes(){ + const formData = this.formActionContext.save(); + const id = this.state.flow?.recordId || this.state.flow?.workId || ''; + return await this.api.processNodes({ + id, + formData, + }); + } + + + public async action(actionId:string) { + const recordId = this.state.flow?.recordId; + const workId = this.state.flow?.workId; + const formData = this.formActionContext.save(); + if(recordId){ + const request = { + formData, + recordId, + advice:{ + actionId, + } + } + await this.api.action(request); + }else { + const createRequest = { + workId, + formData, + actionId, + } + const recordId = await this.api.create(createRequest); + const actionRequest = { + formData, + recordId, + advice:{ + actionId, + } + } + await this.api.action(actionRequest); + } + } + +} \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/presenters/form.ts b/frontend/packages/flow-design/src/components/flow-approval/presenters/form.ts new file mode 100644 index 00000000..68b0c689 --- /dev/null +++ b/frontend/packages/flow-design/src/components/flow-approval/presenters/form.ts @@ -0,0 +1,39 @@ +export interface IFormAction { + save(): any; + + key(): string; +} + + +export class FormActionContext { + + private readonly formActions: IFormAction[]; + + constructor() { + this.formActions = []; + } + + public addAction(submit: IFormAction) { + const keys = this.formActions.map(item => item.key()); + if (keys.includes(submit.key())) { + return; + } + this.formActions.push(submit); + } + + public removeAction(key: string) { + const index = this.formActions.findIndex(item => item.key() === key); + if (index !== -1) { + this.formActions.splice(index, 1); + } + } + + public save() { + let value = {}; + for (const form of this.formActions) { + const data = form.save(); + value = Object.assign(value, data); + } + return value; + } +} \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/presenters/index.ts b/frontend/packages/flow-design/src/components/flow-approval/presenters/index.ts index 99603fa7..68524fef 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/presenters/index.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/presenters/index.ts @@ -1,24 +1,46 @@ import {FlowApprovalApi, State} from "@/components/flow-approval/typings"; import {Dispatch} from "@flow-engine/flow-core"; +import {FormActionContext} from "@/components/flow-approval/presenters/form"; +import {FlowActionPresenter} from "@/components/flow-approval/presenters/action"; export class Presenter { private state: State; private readonly dispatch: Dispatch; private readonly api: FlowApprovalApi; + private readonly formActionContext:FormActionContext; + private readonly flowActionPresenter:FlowActionPresenter; constructor(state: State, dispatch: Dispatch, api: FlowApprovalApi) { this.state = state; this.dispatch = dispatch; this.api = api; + this.formActionContext = new FormActionContext(); + this.flowActionPresenter = new FlowActionPresenter(state,api,this.formActionContext); } public syncState(state: State) { this.state = state; + this.flowActionPresenter.syncState(state); } + + public getFormActionContext() { + return this.formActionContext; + } + + public getFlowActionPresenter() { + return this.flowActionPresenter; + } + + public initialState(state: State) { this.dispatch(state); } + + public processNodes(){ + return this.flowActionPresenter.processNodes(); + } + } \ No newline at end of file diff --git a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts index 7485f796..b8bc758d 100644 --- a/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts +++ b/frontend/packages/flow-design/src/components/flow-approval/typings/index.ts @@ -18,7 +18,7 @@ export interface FlowMeta { export interface Body{ recordId:number; title:string; - data:Map; + data:Record; recordState:number; flowState:number; } @@ -30,7 +30,7 @@ export interface ActionDisplay{ } export interface FlowAction{ - id:number; + id:string; title:string; type:string; display:ActionDisplay; @@ -53,18 +53,29 @@ export interface History{ updateTime:number; } +export interface FlowOperatorBody{ + advice:string; + signKey:string; + approveTime:number; + flowOperator:FlowOperator; +} -export interface NextNode{ +export interface ProcessNode{ nodeId:string; nodeName:string; nodeType:string; - operators:FlowOperator[]; + history:boolean; + operators:FlowOperatorBody[] } + export interface FlowContent { recordId:number; - workflowCode:string; + workId:string; + workCode:string; view:string; + adviceNullable:boolean; + signable:boolean; form:FlowMeta; todos:Body[]; actions:FlowAction[]; @@ -74,7 +85,6 @@ export interface FlowContent { flowState:number; recordState:number; histories:History[]; - nextNodes:NextNode[]; } export interface ApprovalLayoutProps { @@ -94,4 +104,9 @@ export const initStateData = { export interface FlowApprovalApi{ -} \ No newline at end of file + create(body:Record):Promise; + + processNodes(body:Record):Promise; + + action(body:Record):Promise; +} diff --git a/frontend/packages/flow-design/src/index.ts b/frontend/packages/flow-design/src/index.ts index 98a86886..79cb5094 100644 --- a/frontend/packages/flow-design/src/index.ts +++ b/frontend/packages/flow-design/src/index.ts @@ -2,4 +2,5 @@ export * from '@/components/design-list'; export * from '@/components/design-panel'; export * from '@/components/ui/table'; export * from '@/components/workflow-select'; -export * from '@/components/flow-approval'; \ No newline at end of file +export * from '@/components/flow-approval'; +export * from '@/plugins/view'; \ No newline at end of file diff --git a/frontend/packages/flow-design/src/plugins/view/index.tsx b/frontend/packages/flow-design/src/plugins/view/index.tsx new file mode 100644 index 00000000..558c2c4d --- /dev/null +++ b/frontend/packages/flow-design/src/plugins/view/index.tsx @@ -0,0 +1,31 @@ +import React from "react"; +import {FormInstance} from "antd"; + +export type ViewComponentProps = { + form:FormInstance; + onValuesChange?:(values:any)=>void; +} + +export class ViewPlugin{ + + private readonly cache:Map>; + + private static readonly instance:ViewPlugin = new ViewPlugin(); + + private constructor(){ + this.cache = new Map(); + } + + public static getInstance(){ + return this.instance; + } + + public register(name:string,view:React.ComponentType){ + this.cache.set(name,view); + } + + public get(name:string){ + return this.cache.get(name); + } + +} \ No newline at end of file diff --git a/scripts/.gitignore b/scripts/.gitignore new file mode 100644 index 00000000..8e519c7e --- /dev/null +++ b/scripts/.gitignore @@ -0,0 +1,2 @@ +*.jar +*.db \ No newline at end of file diff --git a/scripts/package.sh b/scripts/package.sh new file mode 100644 index 00000000..3b31c258 --- /dev/null +++ b/scripts/package.sh @@ -0,0 +1,16 @@ +#!/usr/bin/env bash + +cd ../frontend +pnpm i +pnpm run build +pnpm run build:app-pc + + +rm -rf ../flow-engine-example/src/main/resources/static +mkdir -p ../flow-engine-example/src/main/resources/static + +cp -r apps/app-pc/dist/* ../flow-engine-example/src/main/resources/static/ + +cd ../ +mvn clean package -DskipTests +cp ./flow-engine-example/target/*.jar ./scripts/server.jar \ No newline at end of file