From 210b4e54d703cd639435689266a19e35ae1e3ea9 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Wed, 27 Mar 2024 09:16:12 +0800 Subject: [PATCH 1/7] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E5=90=8E=E7=AB=AF=E5=AE=9E?= =?UTF-8?q?=E7=8E=B010%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 44 +++++ .../definition/BpmSimpleModelController.java | 31 ++++ .../vo/simple/BpmSimpleModelNodeVO.java | 37 +++++ .../vo/simple/BpmSimpleModelSaveReqVO.java | 21 +++ .../core/enums/BpmnModelConstants.java | 10 ++ .../flowable/core/util/BpmnModelUtils.java | 156 +++++++++++++++++- .../service/definition/BpmModelService.java | 9 + .../definition/BpmModelServiceImpl.java | 9 +- .../definition/BpmSimpleModelService.java | 18 ++ .../definition/BpmSimpleModelServiceImpl.java | 44 +++++ 10 files changed, 374 insertions(+), 5 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java new file mode 100644 index 000000000..357804fec --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.bpm.enums.definition; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.framework.common.core.IntArrayValuable; +import lombok.AllArgsConstructor; +import lombok.Getter; + +import java.util.Arrays; +import java.util.Objects; + +/** + * 仿钉钉的流程器设计器的模型节点类型 + * + * @author jason + */ +@Getter +@AllArgsConstructor +public enum BpmSimpleModelNodeType implements IntArrayValuable { + + START_NODE(-1, "开始节点"), + START_USER_NODE(0, "发起人结点"), + APPROVE_USER_NODE (1, "审批人节点"), + EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), + END_NODE(-2, "结束节点"); + + public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); + + private final Integer type; + private final String name; + + public static boolean isGatewayNode(Integer type) { + // TODO 后续增加并行网关的支持 + return Objects.equals(EXCLUSIVE_GATEWAY_NODE.getType(), type); + } + + public static BpmSimpleModelNodeType valueOf(Integer type) { + return ArrayUtil.firstMatch(nodeType -> nodeType.getType().equals(type), values()); + } + + @Override + public int[] array() { + return ARRAYS; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java new file mode 100644 index 000000000..f6fb33e16 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java @@ -0,0 +1,31 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition; + +import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.validation.Valid; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; + +@Tag(name = "管理后台 - BPM 仿钉钉流程设计器") +@RestController +@RequestMapping("/bpm/simple") +public class BpmSimpleModelController { + @Resource + private BpmSimpleModelService bpmSimpleModelService; + + @PostMapping("/save") + @Operation(summary = "保存仿钉钉流程设计模型") + @PreAuthorize("@ss.hasPermission('bpm:model:update')") + public CommonResult saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) { + return success(bpmSimpleModelService.saveSimpleModel(reqVO)); + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java new file mode 100644 index 000000000..19152bd2a --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java @@ -0,0 +1,37 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; + +import cn.iocoder.yudao.framework.common.validation.InEnum; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") +@Data +public class BpmSimpleModelNodeVO { + + @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") + @NotEmpty(message = "模型节点编号不能为空") + private String id; + + @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotNull(message = "模型节点类型不能为空") + @InEnum(BpmSimpleModelNodeType.class) + private Integer type; + + @Schema(description = "模型节点名称", example = "领导审批") + private String name; + + @Schema(description = "孩子节点") + private BpmSimpleModelNodeVO childNode; + + @Schema(description = "网关节点的条件节点") + private List conditionNodes; + + @Schema(description = "节点的属性") + private Map attributes; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java new file mode 100644 index 000000000..881adc89f --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java @@ -0,0 +1,21 @@ +package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; + +import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.Valid; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; +import lombok.Data; + +@Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") +@Data +public class BpmSimpleModelSaveReqVO { + + @Schema(description = "流程模型编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @NotEmpty(message = "流程模型编号不能为空") + private String modelId; // 对应 Flowable act_re_model 表 ID_ 字段 + + @Schema(description = "仿钉钉流程设计模型对象", requiredMode = Schema.RequiredMode.REQUIRED) + @NotNull(message = "仿钉钉流程设计模型对象不能为空") + @Valid + private BpmSimpleModelNodeVO simpleModelBody; +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 3eb6981ef..5283baa25 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -23,4 +23,14 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + /** + * BPMN Start Event 节点 Id, 用于后端生成 Start Event 节点 + */ + String START_EVENT_ID = "StartEvent_1"; + + /** + * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 + */ + String END_EVENT_ID = "EndEvent_1"; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index bcf82d731..b66ef6a97 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -1,15 +1,25 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.lang.TypeReference; +import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; +import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; import org.flowable.common.engine.impl.util.io.BytesStreamSource; -import java.util.*; +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; /** * 流程模型转操作工具类 @@ -326,4 +336,148 @@ public class BpmnModelUtils { return userTaskList; } + /** + * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) + * @param processId 流程标识 + * @param processName 流程名称 + * @param simpleModelNode 仿钉钉流程设计模型数据结构 + * @return Bpmn Model + */ + public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName,BpmSimpleModelNodeVO simpleModelNode) { + BpmnModel bpmnModel = new BpmnModel(); + Process mainProcess = new Process(); + mainProcess.setId(processId); + mainProcess.setName(processName); + mainProcess.setExecutable(Boolean.TRUE); + bpmnModel.addProcess(mainProcess); + // 目前前端传进来的节点。 没有 start event 节点 和 end event 节点。 + // 在最前面加上 start event node + BpmSimpleModelNodeVO startEventNode = new BpmSimpleModelNodeVO(); + startEventNode.setId(BpmnModelConstants.START_EVENT_ID); + startEventNode.setName("开始"); + startEventNode.setType(BpmSimpleModelNodeType.START_NODE.getType()); + startEventNode.setChildNode(simpleModelNode); + // 添加 FlowNode + addBpmnFlowNode(mainProcess, startEventNode); + // 单独添加 end event 节点 + addBpmnEndEventNode(mainProcess); + // 添加节点之间的连线 Sequence Flow + addBpmnSequenceFlow(mainProcess, startEventNode); + // 自动布局 + new BpmnAutoLayout(bpmnModel).execute(); + + return bpmnModel; + } + + private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) { + // 节点为 null 退出 + if (node == null) { + return; + } + BpmSimpleModelNodeVO childNode = node.getChildNode(); + // 如果后续节点为 null. 添加与结束节点的连线 + if (childNode == null) { + addBpmnSequenceFlowElement(mainProcess, node.getId(),BpmnModelConstants.END_EVENT_ID, null); + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + switch(nodeType){ + case START_NODE : + case START_USER_NODE : + case APPROVE_USER_NODE : + addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null); + break; + default : { + // TODO 其它节点类型的实现 + } + } + // 递归调用后续节点 + addBpmnSequenceFlow(mainProcess, childNode); + } + + private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) { + SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); + if (StrUtil.isNotEmpty(conditionExpression)) { + sequenceFlow.setConditionExpression(conditionExpression); + } + mainProcess.addFlowElement(sequenceFlow); + + } + + private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) { + // 节点为 null 退出 + if (simpleModelNode == null) { + return; + } + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); + Assert.notNull(nodeType, "模型节点类型不支持"); + switch(nodeType){ + case START_NODE : + addBpmnStartEventNode(mainProcess, simpleModelNode); + break; + case START_USER_NODE : + case APPROVE_USER_NODE : + addBpmnUserTaskEventNode(mainProcess, simpleModelNode); + break; + default : { + // TODO 其它节点类型的实现 + } + } + + // 如果不是网关类型的接口, 并且chileNode为空退出 + if (!BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && simpleModelNode.getChildNode() == null) { + return; + } + + // 如果是网关类型接口. 递归添加条件节点 + if (BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { + for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { + addBpmnFlowNode(mainProcess, node); + } + } + + // chileNode不为空,递归添加子节点 + if (simpleModelNode.getChildNode() != null) { + addBpmnFlowNode(mainProcess, simpleModelNode.getChildNode()); + } + } + + private static void addBpmnEndEventNode(Process mainProcess) { + EndEvent endEvent = new EndEvent(); + endEvent.setId(BpmnModelConstants.END_EVENT_ID); + endEvent.setName("结束"); + mainProcess.addFlowElement(endEvent); + } + + private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { + UserTask userTask = new UserTask(); + userTask.setId(node.getId()); + userTask.setName(node.getName()); + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(),BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); + List candidateParam = MapUtil.get(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, new TypeReference<>() {}); + // 添加自定义属性 + addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, + candidateStrategy == null ? null : String.valueOf(candidateStrategy)); + addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, + CollUtil.join(candidateParam, ",")); + mainProcess.addFlowElement(userTask); + } + + private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { + if (value == null) { + return; + } + ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value); + extensionAttribute.setNamespace(namespace); + element.addAttribute(extensionAttribute); + } + + private static void addBpmnStartEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { + StartEvent startEvent = new StartEvent(); + startEvent.setId(node.getId()); + startEvent.setName(node.getName()); + mainProcess.addFlowElement(startEvent); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index e1acce064..2e0927e7b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -46,6 +46,15 @@ public interface BpmModelService { */ byte[] getModelBpmnXML(String id); + + /** + * 保存流程模型的 BPMN XML + * + * @param id 编号 + * @param bpmnXml BPMN XML + */ + void saveModelBpmnXml(String id, String bpmnXml); + /** * 修改流程模型 * diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index eb392ace2..b85b5e066 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -103,7 +103,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 保存流程定义 repositoryService.saveModel(model); // 保存 BPMN XML - saveModelBpmnXml(model, bpmnXml); + saveModelBpmnXml(model.getId(), bpmnXml); return model.getId(); } @@ -121,7 +121,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 更新模型 repositoryService.saveModel(model); // 更新 BPMN XML - saveModelBpmnXml(model, updateReqVO.getBpmnXml()); + saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); } @Override @@ -236,11 +236,12 @@ public class BpmModelServiceImpl implements BpmModelService { } } - private void saveModelBpmnXml(Model model, String bpmnXml) { + @Override + public void saveModelBpmnXml(String id, String bpmnXml) { if (StrUtil.isEmpty(bpmnXml)) { return; } - repositoryService.addModelEditorSource(model.getId(), StrUtil.utf8Bytes(bpmnXml)); + repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java new file mode 100644 index 000000000..1761ba3ff --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java @@ -0,0 +1,18 @@ +package cn.iocoder.yudao.module.bpm.service.definition; + +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import jakarta.validation.Valid; + +/** + * 仿钉钉流程设计 Service 接口 + * + * @author jason + */ +public interface BpmSimpleModelService { + + /** + * 保存仿钉钉流程设计模型 + * @param reqVO 请求信息 + */ + Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO); +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java new file mode 100644 index 000000000..8273ca176 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -0,0 +1,44 @@ +package cn.iocoder.yudao.module.bpm.service.definition; + +import cn.hutool.core.util.ArrayUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; +import jakarta.annotation.Resource; +import org.flowable.bpmn.model.BpmnModel; +import org.flowable.engine.repository.Model; +import org.springframework.stereotype.Service; +import org.springframework.validation.annotation.Validated; + +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; + +/** + * 仿钉钉流程设计 Service 实现类 + * + * @author jason + */ +@Service +@Validated +public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { + @Resource + private BpmModelService bpmModelService; + + @Override + public Boolean saveSimpleModel(BpmSimpleModelSaveReqVO reqVO) { + Model model = bpmModelService.getModel(reqVO.getModelId()); + if (model == null) { + throw exception(MODEL_NOT_EXISTS); + } + byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId()); + + if (ArrayUtil.isEmpty(bpmnBytes)) { + // BPMN XML 不存在。新增 + BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + bpmModelService.saveModelBpmnXml(model.getId(),BpmnModelUtils.getBpmnXml(bpmnModel)); + return Boolean.TRUE; + } else { + // TODO BPMN XML 已经存在。如何修改 ?? + return Boolean.FALSE; + } + } +} From b504d9841e37c23cfed48640d641d47529fb4314 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 29 Mar 2024 19:49:47 +0800 Subject: [PATCH 2/7] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E5=90=8E=E7=AB=AF=E5=AE=9E?= =?UTF-8?q?=E7=8E=B030%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../module/bpm/enums/ErrorCodeConstants.java | 2 + .../definition/BpmSimpleModelController.java | 15 +- .../core/enums/BpmnModelConstants.java | 15 +- .../flowable/core/util/BpmnModelUtils.java | 57 +++---- .../definition/BpmSimpleModelService.java | 8 + .../definition/BpmSimpleModelServiceImpl.java | 142 ++++++++++++++++-- 6 files changed, 187 insertions(+), 52 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index ec167719c..ba6b8c454 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -75,4 +75,6 @@ public interface ErrorCodeConstants { // ========== BPM 流程表达式 1-009-014-000 ========== ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); + // ========== BPM 仿钉钉流程设计器 1-009-015-000 ========== + ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程"); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java index f6fb33e16..40745513e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java @@ -1,17 +1,16 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition; import cn.iocoder.yudao.framework.common.pojo.CommonResult; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; import cn.iocoder.yudao.module.bpm.service.definition.BpmSimpleModelService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.annotation.Resource; import jakarta.validation.Valid; import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; @@ -28,4 +27,12 @@ public class BpmSimpleModelController { public CommonResult saveSimpleModel(@Valid @RequestBody BpmSimpleModelSaveReqVO reqVO) { return success(bpmSimpleModelService.saveSimpleModel(reqVO)); } + + @GetMapping("/get") + @Operation(summary = "获得仿钉钉流程设计模型") + @Parameter(name = "modelId", description = "流程模型编号", required = true, example = "a2c5eee0-eb6c-11ee-abf4-0c37967c420a") + public CommonResult getSimpleModel(@RequestParam("modelId") String modelId){ + return success(bpmSimpleModelService.getSimpleModel(modelId)); + } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 5283baa25..72d4e59ed 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -1,5 +1,10 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.enums; +import com.google.common.collect.ImmutableSet; +import org.flowable.bpmn.model.*; + +import java.util.Set; + /** * BPMN XML 常量信息 * @@ -23,14 +28,14 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; - /** - * BPMN Start Event 节点 Id, 用于后端生成 Start Event 节点 - */ - String START_EVENT_ID = "StartEvent_1"; - /** * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 */ String END_EVENT_ID = "EndEvent_1"; + /** + * 支持转仿钉钉设计模型的 Bpmn 节点 + */ + Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index b66ef6a97..501cfb6a5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -2,7 +2,6 @@ package cn.iocoder.yudao.module.bpm.framework.flowable.core.util; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; -import cn.hutool.core.lang.TypeReference; import cn.hutool.core.map.MapUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -338,31 +337,26 @@ public class BpmnModelUtils { /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) - * @param processId 流程标识 - * @param processName 流程名称 + * + * @param processId 流程标识 + * @param processName 流程名称 * @param simpleModelNode 仿钉钉流程设计模型数据结构 * @return Bpmn Model */ - public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName,BpmSimpleModelNodeVO simpleModelNode) { + public static BpmnModel convertSimpleModelToBpmnModel(String processId, String processName, BpmSimpleModelNodeVO simpleModelNode) { BpmnModel bpmnModel = new BpmnModel(); Process mainProcess = new Process(); mainProcess.setId(processId); mainProcess.setName(processName); mainProcess.setExecutable(Boolean.TRUE); bpmnModel.addProcess(mainProcess); - // 目前前端传进来的节点。 没有 start event 节点 和 end event 节点。 - // 在最前面加上 start event node - BpmSimpleModelNodeVO startEventNode = new BpmSimpleModelNodeVO(); - startEventNode.setId(BpmnModelConstants.START_EVENT_ID); - startEventNode.setName("开始"); - startEventNode.setType(BpmSimpleModelNodeType.START_NODE.getType()); - startEventNode.setChildNode(simpleModelNode); + // 前端模型数据结构。 有 start event 节点. 没有 end event 节点。 // 添加 FlowNode - addBpmnFlowNode(mainProcess, startEventNode); + addBpmnFlowNode(mainProcess, simpleModelNode); // 单独添加 end event 节点 addBpmnEndEventNode(mainProcess); // 添加节点之间的连线 Sequence Flow - addBpmnSequenceFlow(mainProcess, startEventNode); + addBpmnSequenceFlow(mainProcess, simpleModelNode); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); @@ -371,24 +365,24 @@ public class BpmnModelUtils { private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) { // 节点为 null 退出 - if (node == null) { + if (node == null || node.getId() == null) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); // 如果后续节点为 null. 添加与结束节点的连线 - if (childNode == null) { - addBpmnSequenceFlowElement(mainProcess, node.getId(),BpmnModelConstants.END_EVENT_ID, null); + if (childNode == null || childNode.getId() == null) { + addBpmnSequenceFlowElement(mainProcess, node.getId(), BpmnModelConstants.END_EVENT_ID, null); return; } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - switch(nodeType){ - case START_NODE : - case START_USER_NODE : - case APPROVE_USER_NODE : + switch (nodeType) { + case START_NODE: + case START_USER_NODE: + case APPROVE_USER_NODE: addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null); break; - default : { + default: { // TODO 其它节点类型的实现 } } @@ -396,7 +390,7 @@ public class BpmnModelUtils { addBpmnSequenceFlow(mainProcess, childNode); } - private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) { + private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) { SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); @@ -407,20 +401,20 @@ public class BpmnModelUtils { private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) { // 节点为 null 退出 - if (simpleModelNode == null) { + if (simpleModelNode == null || simpleModelNode.getId() == null) { return; } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); - switch(nodeType){ - case START_NODE : + switch (nodeType) { + case START_NODE: addBpmnStartEventNode(mainProcess, simpleModelNode); break; - case START_USER_NODE : - case APPROVE_USER_NODE : + case START_USER_NODE: + case APPROVE_USER_NODE: addBpmnUserTaskEventNode(mainProcess, simpleModelNode); break; - default : { + default: { // TODO 其它节点类型的实现 } } @@ -454,17 +448,16 @@ public class BpmnModelUtils { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(),BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); - List candidateParam = MapUtil.get(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, new TypeReference<>() {}); + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); // 添加自定义属性 addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, candidateStrategy == null ? null : String.valueOf(candidateStrategy)); addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, - CollUtil.join(candidateParam, ",")); + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); mainProcess.addFlowElement(userTask); } - private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { + private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { if (value == null) { return; } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java index 1761ba3ff..2d6865d24 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; import jakarta.validation.Valid; @@ -15,4 +16,11 @@ public interface BpmSimpleModelService { * @param reqVO 请求信息 */ Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO); + + /** + * 获取仿钉钉流程设计模型结构 + * @param modelId 流程模型编号 + * @return 仿钉钉流程设计模型结构 + */ + BpmSimpleModelNodeVO getSimpleModel(String modelId); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index 8273ca176..eca3da184 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -1,16 +1,28 @@ package cn.iocoder.yudao.module.bpm.service.definition; -import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.map.MapUtil; +import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; +import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants; import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils; import jakarta.annotation.Resource; -import org.flowable.bpmn.model.BpmnModel; +import org.flowable.bpmn.model.*; import org.flowable.engine.repository.Model; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; +import java.util.List; +import java.util.Map; + import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; +import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_NODE; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM; +import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; /** * 仿钉钉流程设计 Service 实现类 @@ -29,16 +41,124 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { if (model == null) { throw exception(MODEL_NOT_EXISTS); } - byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId()); +// byte[] bpmnBytes = bpmModelService.getModelBpmnXML(reqVO.getModelId()); +// if (ArrayUtil.isEmpty(bpmnBytes)) { +// // BPMN XML 不存在。新增 +// BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); +// bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); +// return Boolean.TRUE; +// } else { +// // TODO BPMN XML 已经存在。如何修改 ?? +// return Boolean.FALSE; +// } + // 暂时直接修改 + BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); + bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + return Boolean.TRUE; + } - if (ArrayUtil.isEmpty(bpmnBytes)) { - // BPMN XML 不存在。新增 - BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); - bpmModelService.saveModelBpmnXml(model.getId(),BpmnModelUtils.getBpmnXml(bpmnModel)); - return Boolean.TRUE; - } else { - // TODO BPMN XML 已经存在。如何修改 ?? - return Boolean.FALSE; + @Override + public BpmSimpleModelNodeVO getSimpleModel(String modelId) { + Model model = bpmModelService.getModel(modelId); + if (model == null) { + throw exception(MODEL_NOT_EXISTS); + } + byte[] bpmnBytes = bpmModelService.getModelBpmnXML(modelId); + BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); + return convertBpmnModelToSimpleModel(bpmnModel); + + } + + /** + * Bpmn Model 转换成 仿钉钉流程设计模型数据结构(json) 待完善 + * + * @param bpmnModel Bpmn Model + * @return 仿钉钉流程设计模型数据结构 + */ + private BpmSimpleModelNodeVO convertBpmnModelToSimpleModel(BpmnModel bpmnModel) { + if (bpmnModel == null) { + return null; + } + StartEvent startEvent = BpmnModelUtils.getStartEvent(bpmnModel); + if (startEvent == null) { + return null; + } + BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO(); + rootNode.setType(START_NODE.getType()); + rootNode.setId(startEvent.getId()); + rootNode.setName(startEvent.getName()); + recursiveBuildSimpleModelNode(startEvent, rootNode); + return rootNode; + + } + + private void recursiveBuildSimpleModelNode(FlowNode currentFlowNode, BpmSimpleModelNodeVO currentSimpleModeNode) { + BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(currentSimpleModeNode.getType()); + Assert.notNull(nodeType, "节点类型不支持"); + // 校验节点是否支持转仿钉钉的流程模型 + List outgoingFlows = validateCanConvertSimpleNode(nodeType, currentFlowNode); + if (CollUtil.isEmpty(outgoingFlows) || outgoingFlows.get(0).getTargetFlowElement() == null) { + return; + } + FlowElement targetElement = outgoingFlows.get(0).getTargetFlowElement(); + // 如果是 EndEvent 直接退出 + if (targetElement instanceof EndEvent) { + return; + } + if (targetElement instanceof UserTask) { + BpmSimpleModelNodeVO childNode = convertUserTaskToSimpleModelNode((UserTask) targetElement); + currentSimpleModeNode.setChildNode(childNode); + recursiveBuildSimpleModelNode((FlowNode) targetElement, childNode); + } + // TODO 其它节点类型待实现 + } + + + private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) { + BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO(); + simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType()); + simpleModelNodeVO.setName(userTask.getName()); + simpleModelNodeVO.setId(userTask.getId()); + Map attributes = MapUtil.newHashMap(); + // TODO 暂时是普通审批,需要加会签 + attributes.put("approveMethod", 1); + attributes.computeIfAbsent(USER_TASK_CANDIDATE_STRATEGY, (key) -> BpmnModelUtils.parseCandidateStrategy(userTask)); + attributes.computeIfAbsent(USER_TASK_CANDIDATE_PARAM, (key) -> BpmnModelUtils.parseCandidateParam(userTask)); + simpleModelNodeVO.setAttributes(attributes); + return simpleModelNodeVO; + } + + private List validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { + switch (nodeType) { + case START_NODE: + case APPROVE_USER_NODE: { + List outgoingFlows = currentFlowNode.getOutgoingFlows(); + if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) { + throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); + } + validIsSupportFlowNode(outgoingFlows.get(0).getTargetFlowElement()); + return outgoingFlows; + } + default: { + // TODO 其它节点类型待实现 + throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); + } + } + } + + private void validIsSupportFlowNode(FlowElement targetElement) { + if (targetElement == null) { + return; + } + boolean isSupport = false; + for (Class item : BpmnModelConstants.SUPPORT_CONVERT_SIMPLE_FlOW_NODES) { + if (item.isInstance(targetElement)) { + isSupport = true; + break; + } + } + if (!isSupport) { + throw exception(CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT); } } } From 1b708ccd5567b29b659ca101c5c5720f4bb8e2ce Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 30 Mar 2024 22:13:26 +0800 Subject: [PATCH 3/7] =?UTF-8?q?BPM=EF=BC=9Acode=20review=20=E5=BF=AB?= =?UTF-8?q?=E6=90=AD=E7=9A=84=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java | 2 ++ .../bpm/enums/definition/BpmSimpleModelNodeType.java | 2 ++ .../admin/definition/BpmSimpleModelController.java | 1 + .../admin/definition/vo/simple/BpmSimpleModelNodeVO.java | 5 +++-- .../definition/vo/simple/BpmSimpleModelSaveReqVO.java | 2 ++ .../framework/flowable/core/enums/BpmnModelConstants.java | 3 ++- .../bpm/framework/flowable/core/util/BpmnModelUtils.java | 4 ++-- .../module/bpm/service/definition/BpmModelService.java | 1 + .../bpm/service/definition/BpmSimpleModelService.java | 3 +++ .../bpm/service/definition/BpmSimpleModelServiceImpl.java | 7 +++---- 10 files changed, 21 insertions(+), 9 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java index ba6b8c454..e344a2145 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java @@ -76,5 +76,7 @@ public interface ErrorCodeConstants { ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "流程表达式不存在"); // ========== BPM 仿钉钉流程设计器 1-009-015-000 ========== + // TODO @芋艿:这个错误码,需要关注下 ErrorCode CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT = new ErrorCode(1_009_015_000, "该流程模型不支持仿钉钉设计流程"); + } diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 357804fec..e607a1466 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -17,6 +17,7 @@ import java.util.Objects; @AllArgsConstructor public enum BpmSimpleModelNodeType implements IntArrayValuable { + // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; START_NODE(-1, "开始节点"), START_USER_NODE(0, "发起人结点"), APPROVE_USER_NODE (1, "审批人节点"), @@ -41,4 +42,5 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { public int[] array() { return ARRAYS; } + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java index 40745513e..2f88c6b6d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/BpmSimpleModelController.java @@ -14,6 +14,7 @@ import org.springframework.web.bind.annotation.*; import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success; +// TODO @芋艿:后续考虑下,怎么放这个 Controller @Tag(name = "管理后台 - BPM 仿钉钉流程设计器") @RestController @RequestMapping("/bpm/simple") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java index 19152bd2a..f39c56632 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java @@ -16,7 +16,7 @@ public class BpmSimpleModelNodeVO { @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") @NotEmpty(message = "模型节点编号不能为空") - private String id; + private String id; @Schema(description = "模型节点类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") @NotNull(message = "模型节点类型不能为空") @@ -33,5 +33,6 @@ public class BpmSimpleModelNodeVO { private List conditionNodes; @Schema(description = "节点的属性") - private Map attributes; + private Map attributes; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java index 881adc89f..54e0191d5 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelSaveReqVO.java @@ -6,6 +6,7 @@ import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import lombok.Data; +// TODO @芋艿:或许挪到 model 里的 simple 包 @Schema(description = "管理后台 - 仿钉钉流程设计模型的新增/修改 Request VO") @Data public class BpmSimpleModelSaveReqVO { @@ -18,4 +19,5 @@ public class BpmSimpleModelSaveReqVO { @NotNull(message = "仿钉钉流程设计模型对象不能为空") @Valid private BpmSimpleModelNodeVO simpleModelBody; + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java index 72d4e59ed..7513a64c8 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/enums/BpmnModelConstants.java @@ -28,6 +28,7 @@ public interface BpmnModelConstants { */ String USER_TASK_CANDIDATE_PARAM = "candidateParam"; + // TODO @芋艿:这里后面得关注下; /** * BPMN End Event 节点 Id, 用于后端生成 End Event 节点 */ @@ -36,6 +37,6 @@ public interface BpmnModelConstants { /** * 支持转仿钉钉设计模型的 Bpmn 节点 */ - Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); + Set> SUPPORT_CONVERT_SIMPLE_FlOW_NODES = ImmutableSet.of(UserTask.class, EndEvent.class); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 501cfb6a5..7facfce35 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -335,6 +335,8 @@ public class BpmnModelUtils { return userTaskList; } + // ========== TODO 芋艿:这里得捉摸下; ========== + /** * 仿钉钉流程设计模型数据结构(json) 转换成 Bpmn Model (待完善) * @@ -359,7 +361,6 @@ public class BpmnModelUtils { addBpmnSequenceFlow(mainProcess, simpleModelNode); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); - return bpmnModel; } @@ -396,7 +397,6 @@ public class BpmnModelUtils { sequenceFlow.setConditionExpression(conditionExpression); } mainProcess.addFlowElement(sequenceFlow); - } private static void addBpmnFlowNode(Process mainProcess, BpmSimpleModelNodeVO simpleModelNode) { diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index 2e0927e7b..eb841858e 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -53,6 +53,7 @@ public interface BpmModelService { * @param id 编号 * @param bpmnXml BPMN XML */ + // TODO @芋艿:可能要关注下; void saveModelBpmnXml(String id, String bpmnXml); /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java index 2d6865d24..e06dff7c6 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelService.java @@ -13,14 +13,17 @@ public interface BpmSimpleModelService { /** * 保存仿钉钉流程设计模型 + * * @param reqVO 请求信息 */ Boolean saveSimpleModel(@Valid BpmSimpleModelSaveReqVO reqVO); /** * 获取仿钉钉流程设计模型结构 + * * @param modelId 流程模型编号 * @return 仿钉钉流程设计模型结构 */ BpmSimpleModelNodeVO getSimpleModel(String modelId); + } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index eca3da184..bc34a8acb 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -32,6 +32,7 @@ import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnMode @Service @Validated public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { + @Resource private BpmModelService bpmModelService; @@ -48,7 +49,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { // bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); // return Boolean.TRUE; // } else { -// // TODO BPMN XML 已经存在。如何修改 ?? +// // TODO BPMN XML 已经存在。如何修改 ?? TODO add by 芋艿:感觉一个流程,只能二选一,要么 bpmn、要么 simple // return Boolean.FALSE; // } // 暂时直接修改 @@ -66,9 +67,9 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { byte[] bpmnBytes = bpmModelService.getModelBpmnXML(modelId); BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); return convertBpmnModelToSimpleModel(bpmnModel); - } + // TODO @jason:一般要支持这个么?感觉 bpmn 转 json 支持会不会太复杂。可以优先级低一点,做下调研~ /** * Bpmn Model 转换成 仿钉钉流程设计模型数据结构(json) 待完善 * @@ -89,7 +90,6 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { rootNode.setName(startEvent.getName()); recursiveBuildSimpleModelNode(startEvent, rootNode); return rootNode; - } private void recursiveBuildSimpleModelNode(FlowNode currentFlowNode, BpmSimpleModelNodeVO currentSimpleModeNode) { @@ -113,7 +113,6 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { // TODO 其它节点类型待实现 } - private BpmSimpleModelNodeVO convertUserTaskToSimpleModelNode(UserTask userTask) { BpmSimpleModelNodeVO simpleModelNodeVO = new BpmSimpleModelNodeVO(); simpleModelNodeVO.setType(BpmSimpleModelNodeType.APPROVE_USER_NODE.getType()); From d88718071d649146b428485fe02e51d40f72a47b Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Tue, 2 Apr 2024 20:50:49 +0800 Subject: [PATCH 4/7] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E6=8E=92=E4=BB=96=E7=BD=91?= =?UTF-8?q?=E5=85=B3=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 3 +- .../flowable/core/util/BpmnModelUtils.java | 69 +++++++++++++++---- .../definition/BpmSimpleModelServiceImpl.java | 6 +- 3 files changed, 58 insertions(+), 20 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index e607a1466..75889985e 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -18,8 +18,7 @@ import java.util.Objects; public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; - START_NODE(-1, "开始节点"), - START_USER_NODE(0, "发起人结点"), + START_EVENT_NODE(0, "开始节点"), APPROVE_USER_NODE (1, "审批人节点"), EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), END_NODE(-2, "结束节点"); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 7facfce35..17c36623b 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -358,44 +358,72 @@ public class BpmnModelUtils { // 单独添加 end event 节点 addBpmnEndEventNode(mainProcess); // 添加节点之间的连线 Sequence Flow - addBpmnSequenceFlow(mainProcess, simpleModelNode); + addBpmnSequenceFlow(mainProcess, simpleModelNode, BpmnModelConstants.END_EVENT_ID); // 自动布局 new BpmnAutoLayout(bpmnModel).execute(); return bpmnModel; } - private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node) { + private static void addBpmnSequenceFlow(Process mainProcess, BpmSimpleModelNodeVO node, String endId) { // 节点为 null 退出 if (node == null || node.getId() == null) { return; } BpmSimpleModelNodeVO childNode = node.getChildNode(); - // 如果后续节点为 null. 添加与结束节点的连线 - if (childNode == null || childNode.getId() == null) { - addBpmnSequenceFlowElement(mainProcess, node.getId(), BpmnModelConstants.END_EVENT_ID, null); + // 如果不是网关节点、且后续节点为 null. 添加与结束节点的连线 + if (!BpmSimpleModelNodeType.isGatewayNode(node.getType()) && (childNode == null || childNode.getId() == null)) { + addBpmnSequenceFlowElement(mainProcess, node.getId(), endId, null, null); return; } BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(node.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); switch (nodeType) { - case START_NODE: - case START_USER_NODE: - case APPROVE_USER_NODE: - addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null); + case START_EVENT_NODE: + case APPROVE_USER_NODE: { + addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); + // 递归调用后续节点 + addBpmnSequenceFlow(mainProcess, childNode,endId); break; + } + case EXCLUSIVE_GATEWAY_NODE: { + String gateWayEndId = ( childNode == null || childNode.getId() == null ) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); + List conditionNodes = node.getConditionNodes(); + Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); + for (int i = 0; i < conditionNodes.size(); i++) { + BpmSimpleModelNodeVO item = conditionNodes.get(i); + BpmSimpleModelNodeVO nextNodeOnCondition = getNextNodeOnCondition(item); + if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { + addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(), + String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); + addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId); + } else { + addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId, + String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); + } + } + // 递归调用后续节点 + addBpmnSequenceFlow(mainProcess, childNode, endId); + break; + } default: { // TODO 其它节点类型的实现 } } - // 递归调用后续节点 - addBpmnSequenceFlow(mainProcess, childNode); + } - private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String conditionExpression) { + private static BpmSimpleModelNodeVO getNextNodeOnCondition(BpmSimpleModelNodeVO conditionNode) { + return conditionNode.getChildNode(); + } + + private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) { SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { sequenceFlow.setConditionExpression(conditionExpression); } + if (StrUtil.isNotEmpty(seqFlowId)) { + sequenceFlow.setId(seqFlowId); + } mainProcess.addFlowElement(sequenceFlow); } @@ -407,13 +435,15 @@ public class BpmnModelUtils { BpmSimpleModelNodeType nodeType = BpmSimpleModelNodeType.valueOf(simpleModelNode.getType()); Assert.notNull(nodeType, "模型节点类型不支持"); switch (nodeType) { - case START_NODE: + case START_EVENT_NODE: addBpmnStartEventNode(mainProcess, simpleModelNode); break; - case START_USER_NODE: case APPROVE_USER_NODE: addBpmnUserTaskEventNode(mainProcess, simpleModelNode); break; + case EXCLUSIVE_GATEWAY_NODE: + addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); + break; default: { // TODO 其它节点类型的实现 } @@ -427,7 +457,7 @@ public class BpmnModelUtils { // 如果是网关类型接口. 递归添加条件节点 if (BpmSimpleModelNodeType.isGatewayNode(simpleModelNode.getType()) && ArrayUtil.isNotEmpty(simpleModelNode.getConditionNodes())) { for (BpmSimpleModelNodeVO node : simpleModelNode.getConditionNodes()) { - addBpmnFlowNode(mainProcess, node); + addBpmnFlowNode(mainProcess, node.getChildNode()); } } @@ -437,6 +467,15 @@ public class BpmnModelUtils { } } + private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { + Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); + ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); + exclusiveGateway.setId(node.getId()); + // 条件节点的最后一个条件为 网关的 default sequence flow + exclusiveGateway.setDefaultFlow(String.format("%s_SequenceFlow_%d", node.getId(), node.getConditionNodes().size())); + mainProcess.addFlowElement(exclusiveGateway); + } + private static void addBpmnEndEventNode(Process mainProcess) { EndEvent endEvent = new EndEvent(); endEvent.setId(BpmnModelConstants.END_EVENT_ID); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index bc34a8acb..a632d2911 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -20,7 +20,7 @@ import java.util.Map; import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.CONVERT_TO_SIMPLE_MODEL_NOT_SUPPORT; import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.MODEL_NOT_EXISTS; -import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_NODE; +import static cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType.START_EVENT_NODE; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_PARAM; import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY; @@ -85,7 +85,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { return null; } BpmSimpleModelNodeVO rootNode = new BpmSimpleModelNodeVO(); - rootNode.setType(START_NODE.getType()); + rootNode.setType(START_EVENT_NODE.getType()); rootNode.setId(startEvent.getId()); rootNode.setName(startEvent.getName()); recursiveBuildSimpleModelNode(startEvent, rootNode); @@ -129,7 +129,7 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { private List validateCanConvertSimpleNode(BpmSimpleModelNodeType nodeType, FlowNode currentFlowNode) { switch (nodeType) { - case START_NODE: + case START_EVENT_NODE: case APPROVE_USER_NODE: { List outgoingFlows = currentFlowNode.getOutgoingFlows(); if (CollUtil.isNotEmpty(outgoingFlows) && outgoingFlows.size() > 1) { From d9758636b191a4b7d0201b8d3943c15c5b7b220a Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 5 Apr 2024 12:59:37 +0800 Subject: [PATCH 5/7] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E6=8A=84=E9=80=81=E8=8A=82?= =?UTF-8?q?=E7=82=B9=E5=AE=9E=E7=8E=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../definition/BpmSimpleModelNodeType.java | 4 +- .../flowable/core/util/BpmnModelUtils.java | 58 +++++++++++++------ .../task/BpmProcessInstanceCopyService.java | 6 +- .../BpmProcessInstanceCopyServiceImpl.java | 18 +++--- .../service/task/BpmSimpleNodeService.java | 34 +++++++++++ .../bpm/service/task/BpmTaskServiceImpl.java | 3 +- 6 files changed, 90 insertions(+), 33 deletions(-) create mode 100644 yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java diff --git a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java index 75889985e..4e26d02d9 100644 --- a/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java +++ b/yudao-module-bpm/yudao-module-bpm-api/src/main/java/cn/iocoder/yudao/module/bpm/enums/definition/BpmSimpleModelNodeType.java @@ -20,8 +20,10 @@ public enum BpmSimpleModelNodeType implements IntArrayValuable { // TODO @jaosn:-1、0、1、4、-2 是前端已经定义好的么?感觉未来可以考虑搞成和 BPMN 尽量一致的单词哈;类似 usertask 用户审批; START_EVENT_NODE(0, "开始节点"), APPROVE_USER_NODE (1, "审批人节点"), + // 抄送人节点、对应 BPMN 的 ScriptTask. 使用ScriptTask 原因。好像 ServiceTask 自定义属性不能写入 XML + SCRIPT_TASK_NODE(2, "抄送人节点"), EXCLUSIVE_GATEWAY_NODE(4, "排他网关"), - END_NODE(-2, "结束节点"); + END_EVENT_NODE(-2, "结束节点"); public static final int[] ARRAYS = Arrays.stream(values()).mapToInt(BpmSimpleModelNodeType::getType).toArray(); diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java index 17c36623b..03745adce 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/BpmnModelUtils.java @@ -13,6 +13,7 @@ import org.flowable.bpmn.BpmnAutoLayout; import org.flowable.bpmn.converter.BpmnXMLConverter; import org.flowable.bpmn.model.Process; import org.flowable.bpmn.model.*; +import org.flowable.common.engine.impl.scripting.ScriptingEngines; import org.flowable.common.engine.impl.util.io.BytesStreamSource; import java.util.ArrayList; @@ -20,11 +21,15 @@ import java.util.HashSet; import java.util.List; import java.util.Set; +import static org.flowable.bpmn.constants.BpmnXMLConstants.*; + /** * 流程模型转操作工具类 */ public class BpmnModelUtils { + public static final String BPMN_SIMPLE_COPY_EXECUTION_SCRIPT = "#{bpmSimpleNodeService.copy(execution)}"; + public static Integer parseCandidateStrategy(FlowElement userTask) { return NumberUtils.parseInt(userTask.getAttributeValue( BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY)); @@ -379,26 +384,27 @@ public class BpmnModelUtils { Assert.notNull(nodeType, "模型节点类型不支持"); switch (nodeType) { case START_EVENT_NODE: - case APPROVE_USER_NODE: { + case APPROVE_USER_NODE: + case SCRIPT_TASK_NODE: { addBpmnSequenceFlowElement(mainProcess, node.getId(), childNode.getId(), null, null); // 递归调用后续节点 - addBpmnSequenceFlow(mainProcess, childNode,endId); + addBpmnSequenceFlow(mainProcess, childNode, endId); break; } case EXCLUSIVE_GATEWAY_NODE: { - String gateWayEndId = ( childNode == null || childNode.getId() == null ) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); + String gateWayEndId = (childNode == null || childNode.getId() == null) ? BpmnModelConstants.END_EVENT_ID : childNode.getId(); List conditionNodes = node.getConditionNodes(); Assert.notEmpty(conditionNodes, "网关节点的条件节点不能为空"); for (int i = 0; i < conditionNodes.size(); i++) { BpmSimpleModelNodeVO item = conditionNodes.get(i); - BpmSimpleModelNodeVO nextNodeOnCondition = getNextNodeOnCondition(item); + BpmSimpleModelNodeVO nextNodeOnCondition = item.getChildNode(); if (nextNodeOnCondition != null && nextNodeOnCondition.getId() != null) { addBpmnSequenceFlowElement(mainProcess, node.getId(), nextNodeOnCondition.getId(), - String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); addBpmnSequenceFlow(mainProcess, nextNodeOnCondition, gateWayEndId); } else { addBpmnSequenceFlowElement(mainProcess, node.getId(), gateWayEndId, - String.format("%s_SequenceFlow_%d", node.getId(), i+1), null); + String.format("%s_SequenceFlow_%d", node.getId(), i + 1), null); } } // 递归调用后续节点 @@ -412,10 +418,6 @@ public class BpmnModelUtils { } - private static BpmSimpleModelNodeVO getNextNodeOnCondition(BpmSimpleModelNodeVO conditionNode) { - return conditionNode.getChildNode(); - } - private static void addBpmnSequenceFlowElement(Process mainProcess, String sourceId, String targetId, String seqFlowId, String conditionExpression) { SequenceFlow sequenceFlow = new SequenceFlow(sourceId, targetId); if (StrUtil.isNotEmpty(conditionExpression)) { @@ -439,7 +441,10 @@ public class BpmnModelUtils { addBpmnStartEventNode(mainProcess, simpleModelNode); break; case APPROVE_USER_NODE: - addBpmnUserTaskEventNode(mainProcess, simpleModelNode); + addBpmnUserTaskNode(mainProcess, simpleModelNode); + break; + case SCRIPT_TASK_NODE: + addBpmnScriptTaSskNode(mainProcess, simpleModelNode); break; case EXCLUSIVE_GATEWAY_NODE: addBpmnExclusiveGatewayNode(mainProcess, simpleModelNode); @@ -467,6 +472,25 @@ public class BpmnModelUtils { } } + private static void addBpmnScriptTaSskNode(Process mainProcess, BpmSimpleModelNodeVO node) { + ScriptTask scriptTask = new ScriptTask(); + scriptTask.setId(node.getId()); + scriptTask.setName(node.getName()); + scriptTask.setScriptFormat(ScriptingEngines.DEFAULT_SCRIPTING_LANGUAGE); + scriptTask.setScript(BPMN_SIMPLE_COPY_EXECUTION_SCRIPT); + // 添加自定义属性 + addExtensionAttributes(node, scriptTask); + mainProcess.addFlowElement(scriptTask); + } + + private static void addExtensionAttributes(BpmSimpleModelNodeVO node, FlowElement flowElement) { + Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); + addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, + candidateStrategy == null ? null : String.valueOf(candidateStrategy)); + addExtensionAttributes(flowElement, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, + MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + } + private static void addBpmnExclusiveGatewayNode(Process mainProcess, BpmSimpleModelNodeVO node) { Assert.notEmpty(node.getConditionNodes(), "网关节点的条件节点不能为空"); ExclusiveGateway exclusiveGateway = new ExclusiveGateway(); @@ -483,25 +507,21 @@ public class BpmnModelUtils { mainProcess.addFlowElement(endEvent); } - private static void addBpmnUserTaskEventNode(Process mainProcess, BpmSimpleModelNodeVO node) { + private static void addBpmnUserTaskNode(Process mainProcess, BpmSimpleModelNodeVO node) { UserTask userTask = new UserTask(); userTask.setId(node.getId()); userTask.setName(node.getName()); - Integer candidateStrategy = MapUtil.getInt(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY); - // 添加自定义属性 - addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_STRATEGY, - candidateStrategy == null ? null : String.valueOf(candidateStrategy)); - addExtensionAttribute(userTask, BpmnModelConstants.NAMESPACE, BpmnModelConstants.USER_TASK_CANDIDATE_PARAM, - MapUtil.getStr(node.getAttributes(), BpmnModelConstants.USER_TASK_CANDIDATE_PARAM)); + addExtensionAttributes(node, userTask); mainProcess.addFlowElement(userTask); } - private static void addExtensionAttribute(FlowElement element, String namespace, String name, String value) { + private static void addExtensionAttributes(FlowElement element, String namespace, String name, String value) { if (value == null) { return; } ExtensionAttribute extensionAttribute = new ExtensionAttribute(name, value); extensionAttribute.setNamespace(namespace); + extensionAttribute.setNamespacePrefix(FLOWABLE_EXTENSIONS_PREFIX); element.addAttribute(extensionAttribute); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java index bd84490e8..94df76d4d 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyService.java @@ -17,9 +17,11 @@ public interface BpmProcessInstanceCopyService { * 流程实例的抄送 * * @param userIds 抄送的用户编号 - * @param taskId 流程任务编号 + * @param processInstanceId 流程编号 + * @param taskId 任务编号 + * @param taskName 任务名称 */ - void createProcessInstanceCopy(Collection userIds, String taskId); + void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName); /** * 获得抄送的流程的分页 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java index aba8bd9f1..ce1ddd7fd 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceCopyServiceImpl.java @@ -1,6 +1,5 @@ package cn.iocoder.yudao.module.bpm.service.task; -import cn.hutool.core.util.ObjectUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCopyPageReqVO; import cn.iocoder.yudao.module.bpm.dal.dataobject.task.BpmProcessInstanceCopyDO; @@ -11,7 +10,6 @@ import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.flowable.engine.repository.ProcessDefinition; import org.flowable.engine.runtime.ProcessInstance; -import org.flowable.task.api.Task; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import org.springframework.validation.annotation.Validated; @@ -47,14 +45,14 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy private BpmProcessDefinitionService processDefinitionService; @Override - public void createProcessInstanceCopy(Collection userIds, String taskId) { - // 1.1 校验任务存在 - Task task = taskService.getTask(taskId); - if (ObjectUtil.isNull(task)) { - throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); - } + public void createProcessInstanceCopy(Collection userIds, String processInstanceId, String taskId, String taskName) { + // 1.1 校验任务存在 暂时去掉这个校验. 因为任务可能仿钉钉快搭的抄送节点(ScriptTask) +// Task task = taskService.getTask(taskId); +// if (ObjectUtil.isNull(task)) { +// throw exception(ErrorCodeConstants.TASK_NOT_EXISTS); +// } // 1.2 校验流程实例存在 - String processInstanceId = task.getProcessInstanceId(); +// String processInstanceId = task.getProcessInstanceId(); ProcessInstance processInstance = processInstanceService.getProcessInstance(processInstanceId); if (processInstance == null) { throw exception(ErrorCodeConstants.PROCESS_INSTANCE_NOT_EXISTS); @@ -70,7 +68,7 @@ public class BpmProcessInstanceCopyServiceImpl implements BpmProcessInstanceCopy List copyList = convertList(userIds, userId -> new BpmProcessInstanceCopyDO() .setUserId(userId).setStartUserId(Long.valueOf(processInstance.getStartUserId())) .setProcessInstanceId(processInstanceId).setProcessInstanceName(processInstance.getName()) - .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(task.getName())); + .setCategory(processDefinition.getCategory()).setTaskId(taskId).setTaskName(taskName)); processInstanceCopyMapper.insertBatch(copyList); } diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java new file mode 100644 index 000000000..89ba0ee84 --- /dev/null +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmSimpleNodeService.java @@ -0,0 +1,34 @@ +package cn.iocoder.yudao.module.bpm.service.task; + +import cn.iocoder.yudao.module.bpm.framework.flowable.core.candidate.BpmTaskCandidateInvoker; +import jakarta.annotation.Resource; +import org.flowable.bpmn.model.FlowElement; +import org.flowable.engine.delegate.DelegateExecution; +import org.springframework.stereotype.Service; + +import java.util.Set; + +/** + * 仿钉钉快搭各个节点 Service + * @author jason + */ +@Service +public class BpmSimpleNodeService { + + @Resource + private BpmTaskCandidateInvoker taskCandidateInvoker; + @Resource + private BpmProcessInstanceCopyService processInstanceCopyService; + + /** + * 仿钉钉快搭抄送 + * @param execution 执行的任务(ScriptTask) + */ + public Boolean copy(DelegateExecution execution) { + Set userIds = taskCandidateInvoker.calculateUsers(execution); + FlowElement currentFlowElement = execution.getCurrentFlowElement(); + processInstanceCopyService.createProcessInstanceCopy(userIds, execution.getProcessInstanceId(), + currentFlowElement.getId(), currentFlowElement.getName()); + return Boolean.TRUE; + } +} diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java index 7afed756b..5c2de9f48 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmTaskServiceImpl.java @@ -185,7 +185,8 @@ public class BpmTaskServiceImpl implements BpmTaskService { // 2. 抄送用户 if (CollUtil.isNotEmpty(reqVO.getCopyUserIds())) { - processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), reqVO.getId()); + processInstanceCopyService.createProcessInstanceCopy(reqVO.getCopyUserIds(), instance.getProcessInstanceId(), + reqVO.getId(), task.getName()); } // 情况一:被委派的任务,不调用 complete 去完成任务 From 32809c3edb78b52df98970887ca03be5a1af7855 Mon Sep 17 00:00:00 2001 From: jason <2667446@qq.com> Date: Fri, 5 Apr 2024 22:49:34 +0800 Subject: [PATCH 6/7] =?UTF-8?q?=E4=BB=BF=E9=92=89=E9=92=89=E6=B5=81?= =?UTF-8?q?=E7=A8=8B=E8=AE=BE=E8=AE=A1-=E4=BB=BF=E9=92=89=E9=92=89?= =?UTF-8?q?=E5=BF=AB=E6=90=AD=E6=A8=A1=E5=9E=8B=20JSON=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E4=BF=9D=E5=AD=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../vo/simple/BpmSimpleModelNodeVO.java | 2 ++ .../service/definition/BpmModelService.java | 18 ++++++++++++-- .../definition/BpmModelServiceImpl.java | 24 +++++++++++++++---- .../definition/BpmSimpleModelServiceImpl.java | 17 +++++++++---- 4 files changed, 49 insertions(+), 12 deletions(-) diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java index f39c56632..09db4764c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/definition/vo/simple/BpmSimpleModelNodeVO.java @@ -2,6 +2,7 @@ package cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple; import cn.iocoder.yudao.framework.common.validation.InEnum; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; +import com.fasterxml.jackson.annotation.JsonInclude; import io.swagger.v3.oas.annotations.media.Schema; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; @@ -12,6 +13,7 @@ import java.util.Map; @Schema(description = "管理后台 - 仿钉钉流程设计模型节点 VO") @Data +@JsonInclude(JsonInclude.Include.NON_NULL) public class BpmSimpleModelNodeVO { @Schema(description = "模型节点编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "StartEvent_1") diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java index eb841858e..c5f1c962c 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelService.java @@ -51,10 +51,24 @@ public interface BpmModelService { * 保存流程模型的 BPMN XML * * @param id 编号 - * @param bpmnXml BPMN XML + * @param xmlBytes BPMN XML bytes */ // TODO @芋艿:可能要关注下; - void saveModelBpmnXml(String id, String bpmnXml); + void saveModelBpmnXml(String id, byte[] xmlBytes); + + /** + * 获得仿钉钉快搭模型的 JSON 数据 + * @param id 编号 + * @return JSON bytes + */ + byte[] getModelSimpleJson(String id); + + /** + * 保存仿钉钉快搭模型的 JSON 数据 + * @param id 编号 + * @param jsonBytes JSON bytes + */ + void saveModelSimpleJson(String id, byte[] jsonBytes); /** * 修改流程模型 diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java index b85b5e066..a86414762 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmModelServiceImpl.java @@ -1,5 +1,6 @@ package cn.iocoder.yudao.module.bpm.service.definition; +import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.yudao.framework.common.pojo.PageResult; import cn.iocoder.yudao.framework.common.util.json.JsonUtils; @@ -103,7 +104,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 保存流程定义 repositoryService.saveModel(model); // 保存 BPMN XML - saveModelBpmnXml(model.getId(), bpmnXml); + saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(bpmnXml)); return model.getId(); } @@ -121,7 +122,7 @@ public class BpmModelServiceImpl implements BpmModelService { // 更新模型 repositoryService.saveModel(model); // 更新 BPMN XML - saveModelBpmnXml(model.getId(), updateReqVO.getBpmnXml()); + saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(updateReqVO.getBpmnXml())); } @Override @@ -237,11 +238,24 @@ public class BpmModelServiceImpl implements BpmModelService { } @Override - public void saveModelBpmnXml(String id, String bpmnXml) { - if (StrUtil.isEmpty(bpmnXml)) { + public void saveModelBpmnXml(String id, byte[] xmlBytes) { + if (ArrayUtil.isEmpty(xmlBytes)) { return; } - repositoryService.addModelEditorSource(id, StrUtil.utf8Bytes(bpmnXml)); + repositoryService.addModelEditorSource(id, xmlBytes); + } + + @Override + public byte[] getModelSimpleJson(String id) { + return repositoryService.getModelEditorSourceExtra(id); + } + + @Override + public void saveModelSimpleJson(String id, byte[] jsonBytes) { + if (ArrayUtil.isEmpty(jsonBytes)) { + return; + } + repositoryService.addModelEditorSourceExtra(id, jsonBytes); } /** diff --git a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java index a632d2911..cbdabbf7a 100644 --- a/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java +++ b/yudao-module-bpm/yudao-module-bpm-biz/src/main/java/cn/iocoder/yudao/module/bpm/service/definition/BpmSimpleModelServiceImpl.java @@ -3,6 +3,8 @@ package cn.iocoder.yudao.module.bpm.service.definition; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelNodeVO; import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.simple.BpmSimpleModelSaveReqVO; import cn.iocoder.yudao.module.bpm.enums.definition.BpmSimpleModelNodeType; @@ -52,9 +54,12 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { // // TODO BPMN XML 已经存在。如何修改 ?? TODO add by 芋艿:感觉一个流程,只能二选一,要么 bpmn、要么 simple // return Boolean.FALSE; // } - // 暂时直接修改 + // 1. JSON 转换成 bpmnModel BpmnModel bpmnModel = BpmnModelUtils.convertSimpleModelToBpmnModel(model.getKey(), model.getName(), reqVO.getSimpleModelBody()); - bpmModelService.saveModelBpmnXml(model.getId(), BpmnModelUtils.getBpmnXml(bpmnModel)); + // 2.1 保存 Bpmn XML + bpmModelService.saveModelBpmnXml(model.getId(), StrUtil.utf8Bytes(BpmnModelUtils.getBpmnXml(bpmnModel))); + // 2.2 保存 JSON 数据 + bpmModelService.saveModelSimpleJson(model.getId(), JsonUtils.toJsonByte(reqVO.getSimpleModelBody())); return Boolean.TRUE; } @@ -64,12 +69,14 @@ public class BpmSimpleModelServiceImpl implements BpmSimpleModelService { if (model == null) { throw exception(MODEL_NOT_EXISTS); } - byte[] bpmnBytes = bpmModelService.getModelBpmnXML(modelId); - BpmnModel bpmnModel = BpmnModelUtils.getBpmnModel(bpmnBytes); - return convertBpmnModelToSimpleModel(bpmnModel); + // 暂时不用 bpmn 转 json, 有点复杂, + // 通过 ACT_RE_MODEL 表 EDITOR_SOURCE_EXTRA_VALUE_ID_ 获取 仿钉钉快搭模型的JSON 数据 + byte[] jsonBytes = bpmModelService.getModelSimpleJson(model.getId()); + return JsonUtils.parseObject(jsonBytes, BpmSimpleModelNodeVO.class); } // TODO @jason:一般要支持这个么?感觉 bpmn 转 json 支持会不会太复杂。可以优先级低一点,做下调研~ + /** * Bpmn Model 转换成 仿钉钉流程设计模型数据结构(json) 待完善 * From d9407ab40af47fddba87fb8a942622bd154b054d Mon Sep 17 00:00:00 2001 From: dhb52 Date: Tue, 9 Apr 2024 00:07:42 +0800 Subject: [PATCH 7/7] =?UTF-8?q?feat:=20=E5=AE=A2=E6=88=B7=E6=88=90?= =?UTF-8?q?=E4=BA=A4=E5=91=A8=E6=9C=9F=E5=88=86=E6=9E=90(=E6=8C=89?= =?UTF-8?q?=E5=8C=BA=E5=9F=9F=E3=80=81=E6=8C=89=E4=BA=A7=E5=93=81)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../CrmStatisticsCustomerController.http | 10 ++++ .../CrmStatisticsCustomerController.java | 14 ++++- ...atisticsCustomerDealCycleByAreaRespVO.java | 24 +++++++++ ...sticsCustomerDealCycleByProductRespVO.java | 19 +++++++ .../CrmStatisticsCustomerMapper.java | 17 ++++++ .../CrmStatisticsCustomerService.java | 18 ++++++- .../CrmStatisticsCustomerServiceImpl.java | 49 +++++++++++++++++ .../CrmStatisticsCustomerMapper.xml | 52 ++++++++++++++++--- 8 files changed, 195 insertions(+), 8 deletions(-) create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByAreaRespVO.java create mode 100644 yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByProductRespVO.java diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http index 389bf4ac9..6b960512d 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.http @@ -53,3 +53,13 @@ tenant-id: {{adminTenentId}} GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-user?deptId=100×[0]=2023-01-01 00:00:00×[1]=2024-12-12 23:59:59 Authorization: Bearer {{token}} tenant-id: {{adminTenentId}} + +### 6.3 获取客户成交周期(按区域) +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-area?deptId=100×[0]=2023-01-01 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} + +### 6.4 获取客户成交周期(按产品) +GET {{baseUrl}}/crm/statistics-customer/get-customer-deal-cycle-by-product?deptId=100×[0]=2023-01-01 00:00:00×[1]=2024-12-12 23:59:59 +Authorization: Bearer {{token}} +tenant-id: {{adminTenentId}} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java index 51d149900..3539b5db5 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/CrmStatisticsCustomerController.java @@ -96,6 +96,18 @@ public class CrmStatisticsCustomerController { return success(customerService.getCustomerDealCycleByUser(reqVO)); } - // TODO dhb52:【成交周期分析】里,有按照员工(已实现)、地区(未实现)、产品(未实现),需要在看看哈;可以把 CustomerDealCycle 拆成 3 个 tab,员工客户成交周期分析、地区客户成交周期分析、产品客户成交周期分析; + @GetMapping("/get-customer-deal-cycle-by-area") + @Operation(summary = "获取客户成交周期(按用户)") + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')") + public CommonResult> getCustomerDealCycleByArea(@Valid CrmStatisticsCustomerReqVO reqVO) { + return success(customerService.getCustomerDealCycleByArea(reqVO)); + } + + @GetMapping("/get-customer-deal-cycle-by-product") + @Operation(summary = "获取客户成交周期(按用户)") + @PreAuthorize("@ss.hasPermission('crm:statistics-customer:query')") + public CommonResult> getCustomerDealCycleByProduct(@Valid CrmStatisticsCustomerReqVO reqVO) { + return success(customerService.getCustomerDealCycleByProduct(reqVO)); + } } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByAreaRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByAreaRespVO.java new file mode 100644 index 000000000..369837827 --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByAreaRespVO.java @@ -0,0 +1,24 @@ +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer; + +import com.fasterxml.jackson.annotation.JsonIgnore; +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户成交周期分析(按区域) VO") +@Data +public class CrmStatisticsCustomerDealCycleByAreaRespVO { + + @Schema(description = "省份编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + @JsonIgnore + private Integer areaId; + + @Schema(description = "省份名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "浙江省") + private String areaName; + + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0") + private Double customerDealCycle; + + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer customerDealCount; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByProductRespVO.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByProductRespVO.java new file mode 100644 index 000000000..442c195aa --- /dev/null +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/statistics/vo/customer/CrmStatisticsCustomerDealCycleByProductRespVO.java @@ -0,0 +1,19 @@ +package cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer; + +import io.swagger.v3.oas.annotations.media.Schema; +import lombok.Data; + +@Schema(description = "管理后台 - CRM 客户成交周期分析(按产品) VO") +@Data +public class CrmStatisticsCustomerDealCycleByProductRespVO { + + @Schema(description = "产品名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "演示产品") + private String productName; + + @Schema(description = "成交周期", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.0") + private Double customerDealCycle; + + @Schema(description = "成交客户数", requiredMode = Schema.RequiredMode.REQUIRED, example = "1") + private Integer customerDealCount; + +} diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java index 458ef79c3..171d432b0 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/dal/mysql/statistics/CrmStatisticsCustomerMapper.java @@ -53,6 +53,7 @@ public interface CrmStatisticsCustomerMapper { /** * 合同总金额(按用户) + * * @return 统计数据@return 统计数据@param reqVO 请求参数 * @return 统计数据 */ @@ -191,4 +192,20 @@ public interface CrmStatisticsCustomerMapper { */ List selectCustomerDealCycleGroupByUser(CrmStatisticsCustomerReqVO reqVO); + /** + * 客户成交周期(按区域) + * + * @param reqVO 请求参数 + * @return 统计数据 + */ + List selectCustomerDealCycleGroupByAreaId(CrmStatisticsCustomerReqVO reqVO); + + /** + * 客户成交周期(按产品) + * + * @param reqVO 请求参数 + * @return 统计数据 + */ + List selectCustomerDealCycleGroupByProductId(CrmStatisticsCustomerReqVO reqVO); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java index 0e00e9c22..56ecf975e 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerService.java @@ -77,7 +77,7 @@ public interface CrmStatisticsCustomerService { /** * 客户成交周期(按日期) - * + *

* 成交周期的定义:客户 customer 在创建出来,到合同 contract 第一次成交的时间差 * * @param reqVO 请求参数 @@ -93,4 +93,20 @@ public interface CrmStatisticsCustomerService { */ List getCustomerDealCycleByUser(CrmStatisticsCustomerReqVO reqVO); + /** + * 客户成交周期(按区域) + * + * @param reqVO 请求参数 + * @return 统计数据 + */ + List getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO); + + /** + * 客户成交周期(按产品) + * + * @param reqVO 请求参数 + * @return 统计数据 + */ + List getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO); + } diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java index f4787b20f..c5b933c43 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/java/cn/iocoder/yudao/module/crm/service/statistics/CrmStatisticsCustomerServiceImpl.java @@ -4,6 +4,9 @@ import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ObjUtil; import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils; import cn.iocoder.yudao.framework.common.util.number.NumberUtils; +import cn.iocoder.yudao.framework.ip.core.Area; +import cn.iocoder.yudao.framework.ip.core.enums.AreaTypeEnum; +import cn.iocoder.yudao.framework.ip.core.utils.AreaUtils; import cn.iocoder.yudao.module.crm.controller.admin.statistics.vo.customer.*; import cn.iocoder.yudao.module.crm.dal.mysql.statistics.CrmStatisticsCustomerMapper; import cn.iocoder.yudao.module.system.api.dept.DeptApi; @@ -19,6 +22,7 @@ import java.time.LocalDateTime; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.function.Function; import java.util.stream.Stream; import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*; @@ -290,6 +294,51 @@ public class CrmStatisticsCustomerServiceImpl implements CrmStatisticsCustomerSe return summaryList; } + @Override + public List getCustomerDealCycleByArea(CrmStatisticsCustomerReqVO reqVO) { + // 1. 获得用户编号数组 + List userIds = getUserIds(reqVO); + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + reqVO.setUserIds(userIds); + + // 2. 获取客户地区统计数据 + List dealCycleByAreaList = customerMapper.selectCustomerDealCycleGroupByAreaId(reqVO); + if (CollUtil.isEmpty(dealCycleByAreaList)) { + return Collections.emptyList(); + } + + // 3. 拼接数据 + Map areaMap = convertMap(AreaUtils.getByType(AreaTypeEnum.PROVINCE, Function.identity()), + Area::getId); + return convertList(dealCycleByAreaList, vo -> { + if (vo.getAreaId() != null) { + Integer parentId = AreaUtils.getParentIdByType(vo.getAreaId(), AreaTypeEnum.PROVINCE); + findAndThen(areaMap, parentId, area -> vo.setAreaId(parentId).setAreaName(area.getName())); + } + return vo; + }); + } + + @Override + public List getCustomerDealCycleByProduct(CrmStatisticsCustomerReqVO reqVO) { + // 1. 获得用户编号数组 + List userIds = getUserIds(reqVO); + if (CollUtil.isEmpty(userIds)) { + return Collections.emptyList(); + } + reqVO.setUserIds(userIds); + + // 2. 获取客户产品统计数据 + List dealCycleByProductList = customerMapper.selectCustomerDealCycleGroupByProductId(reqVO); + if (CollUtil.isEmpty(dealCycleByProductList)) { + return Collections.emptyList(); + } + + return dealCycleByProductList; + } + /** * 拼接用户信息(昵称) * diff --git a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml index 44c6c4b84..e850a0c88 100644 --- a/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml +++ b/yudao-module-crm/yudao-module-crm-biz/src/main/resources/mapper/statistics/CrmStatisticsCustomerMapper.xml @@ -19,17 +19,18 @@ @@ -53,13 +54,14 @@ COUNT(DISTINCT customer.id) AS customer_deal_count FROM crm_customer AS customer LEFT JOIN crm_contract AS contract ON contract.customer_id = customer.id - WHERE customer.deleted = 0 AND contract.deleted = 0 + WHERE customer.deleted = 0 + AND contract.deleted = 0 AND contract.audit_status = ${@cn.iocoder.yudao.module.crm.enums.common.CrmAuditStatusEnum@APPROVE.status} AND customer.owner_user_id IN #{userId} - AND contract.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} + AND customer.create_time BETWEEN #{times[0],javaType=java.time.LocalDateTime} AND #{times[1],javaType=java.time.LocalDateTime} GROUP BY customer.owner_user_id @@ -221,4 +223,42 @@ GROUP BY customer.owner_user_id + + + +