# Conflicts:
#	yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/contract/listener/CrmContractStatusListener.java
#	yudao-module-crm/yudao-module-crm-server/src/main/java/cn/iocoder/yudao/module/crm/service/receivable/listener/CrmReceivableStatusListener.java
pull/150/MERGE
YunaiV 2025-07-30 19:07:02 +08:00
commit 02ddf5a5a7
17 changed files with 286 additions and 59 deletions

View File

@ -17,6 +17,7 @@ uv run --with simple-ddl-parser convertor.py dm8 > ../dm/ruoyi-vue-pro-dm8.sql
import argparse
import pathlib
import re
import sys
import time
from abc import ABC, abstractmethod
from typing import Dict, Generator, Optional, Tuple, Union
@ -293,8 +294,10 @@ class Convertor(ABC):
# 将parse失败的脚本打印出来
if error_scripts:
print("!!! 以下内容无法正常解析", file=sys.stderr)
for script in error_scripts:
print(script)
# print to stderr
print(script, file=sys.stderr)
class PostgreSQLConvertor(Convertor):

View File

@ -15,6 +15,7 @@ import org.springframework.boot.autoconfigure.web.client.RestTemplateAutoConfigu
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.boot.web.client.RestTemplateBuilder;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.cloud.client.loadbalancer.LoadBalanced;
import org.springframework.context.annotation.Bean;
import org.springframework.util.AntPathMatcher;
import org.springframework.web.bind.annotation.RestController;
@ -124,6 +125,7 @@ public class YudaoWebAutoConfiguration implements WebMvcConfigurer {
*/
@Bean
@ConditionalOnMissingBean
@LoadBalanced
public RestTemplate restTemplate(RestTemplateBuilder restTemplateBuilder) {
return restTemplateBuilder.build();
}

View File

@ -25,6 +25,7 @@ import org.springframework.util.Assert;
import org.springframework.validation.BindException;
import org.springframework.validation.FieldError;
import org.springframework.validation.ObjectError;
import org.springframework.web.HttpMediaTypeNotSupportedException;
import org.springframework.web.HttpRequestMethodNotSupportedException;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.MissingServletRequestParameterException;
@ -100,6 +101,9 @@ public class GlobalExceptionHandler {
if (ex instanceof HttpRequestMethodNotSupportedException) {
return httpRequestMethodNotSupportedExceptionHandler((HttpRequestMethodNotSupportedException) ex);
}
if (ex instanceof HttpMediaTypeNotSupportedException) {
return httpMediaTypeNotSupportedExceptionHandler((HttpMediaTypeNotSupportedException) ex);
}
if (ex instanceof ServiceException) {
return serviceExceptionHandler((ServiceException) ex);
}
@ -170,17 +174,19 @@ public class GlobalExceptionHandler {
/**
* SpringMVC
*
* @RequestBody xx Integer xx String
* @RequestBody xx Integer xx String
*/
@ExceptionHandler(HttpMessageNotReadableException.class)
public CommonResult<?> methodArgumentTypeInvalidFormatExceptionHandler(HttpMessageNotReadableException ex) {
log.warn("[methodArgumentTypeInvalidFormatExceptionHandler]", ex);
if(ex.getCause() instanceof InvalidFormatException) {
if (ex.getCause() instanceof InvalidFormatException) {
InvalidFormatException invalidFormatException = (InvalidFormatException) ex.getCause();
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求参数类型错误:%s", invalidFormatException.getValue()));
}else {
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
if (StrUtil.startWith(ex.getMessage(), "Required request body is missing")) {
return CommonResult.error(BAD_REQUEST.getCode(), "请求参数类型错误: request body 缺失");
}
return defaultExceptionHandler(ServletUtils.getRequest(), ex);
}
/**
@ -236,6 +242,17 @@ public class GlobalExceptionHandler {
return CommonResult.error(METHOD_NOT_ALLOWED.getCode(), String.format("请求方法不正确:%s", ex.getMessage()));
}
/**
* SpringMVC Content-Type
*
* A Content-Type application/json Content-Type application/octet-stream
*/
@ExceptionHandler(HttpMediaTypeNotSupportedException.class)
public CommonResult<?> httpMediaTypeNotSupportedExceptionHandler(HttpMediaTypeNotSupportedException ex) {
log.warn("[httpMediaTypeNotSupportedExceptionHandler]", ex);
return CommonResult.error(BAD_REQUEST.getCode(), String.format("请求类型不正确:%s", ex.getMessage()));
}
/**
* Spring Security
*
@ -394,7 +411,7 @@ public class GlobalExceptionHandler {
return CommonResult.error(NOT_IMPLEMENTED.getCode(),
"[AI 大模型 yudao-module-ai - 表结构未导入][参考 https://cloud.iocoder.cn/ai/build/ 开启]");
}
// 9. IOT 物联网
// 9. IoT 物联网
if (message.contains("iot_")) {
log.error("[IoT 物联网 yudao-module-iot - 表结构未导入][参考 https://doc.iocoder.cn/iot/build/ 开启]");
return CommonResult.error(NOT_IMPLEMENTED.getCode(),

View File

@ -35,6 +35,11 @@ public class BpmProcessInstanceStatusEvent extends ApplicationEvent {
*/
private String businessKey;
public BpmProcessInstanceStatusEvent() {
// new Object() 保证非空
super(new Object());
}
public BpmProcessInstanceStatusEvent(Object source) {
super(source);
}

View File

@ -40,7 +40,7 @@ public interface ErrorCodeConstants {
ErrorCode PROCESS_INSTANCE_START_USER_SELECT_ASSIGNEES_NOT_EXISTS = new ErrorCode(1_009_004_004, "任务({})的候选人({})不存在");
ErrorCode PROCESS_INSTANCE_START_USER_CAN_START = new ErrorCode(1_009_004_005, "发起流程失败,你没有权限发起该流程");
ErrorCode PROCESS_INSTANCE_CANCEL_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_005, "流程取消失败,该流程不允许取消");
ErrorCode PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 触发器请求调用失败");
ErrorCode PROCESS_INSTANCE_HTTP_CALL_ERROR = new ErrorCode(1_009_004_006, "流程 Http 请求调用失败");
ErrorCode PROCESS_INSTANCE_APPROVE_USER_SELECT_ASSIGNEES_NOT_CONFIG = new ErrorCode(1_009_004_007, "下一个任务({})的审批人未配置");
ErrorCode PROCESS_INSTANCE_CANCEL_CHILD_FAIL_NOT_ALLOW = new ErrorCode(1_009_004_008, "子流程取消失败,子流程不允许取消");
@ -58,7 +58,6 @@ public interface ErrorCodeConstants {
ErrorCode TASK_SIGN_DELETE_NO_PARENT = new ErrorCode(1_009_005_012, "任务减签失败,被减签的任务必须是通过加签生成的任务");
ErrorCode TASK_TRANSFER_FAIL_USER_REPEAT = new ErrorCode(1_009_005_013, "任务转办失败,转办人和当前审批人为同一人");
ErrorCode TASK_TRANSFER_FAIL_USER_NOT_EXISTS = new ErrorCode(1_009_005_014, "任务转办失败,转办人不存在");
ErrorCode TASK_CREATE_FAIL_NO_CANDIDATE_USER = new ErrorCode(1_009_006_003, "操作失败,原因:找不到任务的审批人!");
ErrorCode TASK_SIGNATURE_NOT_EXISTS = new ErrorCode(1_009_005_015, "签名不能为空!");
ErrorCode TASK_REASON_REQUIRE = new ErrorCode(1_009_005_016, "审批意见不能为空!");

View File

@ -14,6 +14,7 @@ import lombok.Getter;
@AllArgsConstructor
public enum BpmTaskStatusEnum {
SKIP(-2, "跳过"),
NOT_START(-1, "未开始"),
RUNNING(1, "审批中"),
APPROVE(2, "审批通过"),

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.bpm.api.event;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
/**
*
*
* @author
*/
public class CrmContractStatusListener extends BpmProcessInstanceStatusEventListener {
@Override
public String getProcessDefinitionKey() {
return "crm-contract-audit";
}
@Override
public void onEvent(@RequestBody @Valid BpmProcessInstanceStatusEvent event) {
BpmHttpRequestUtils.executeBpmHttpRequest(event,
"http://crm-server/rpc-api/crm/contract/update-audit-status");
}
}

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.bpm.api.event;
import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmHttpRequestUtils;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.RequestBody;
/**
*
*
* @author
*/
public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventListener {
@Override
public String getProcessDefinitionKey() {
return "crm-receivable-audit";
}
@Override
public void onEvent(@RequestBody @Valid BpmProcessInstanceStatusEvent event) {
BpmHttpRequestUtils.executeBpmHttpRequest(event,
"http://crm-server/rpc-api/crm/receivable/update-audit-status");
}
}

View File

@ -71,6 +71,9 @@ public class BpmSimpleModelNodeVO {
@Schema(description = "是否填写审批意见", example = "false")
private Boolean reasonRequire;
@Schema(description = "跳过表达式", example = "{true}")
private String skipExpression; // 用于审批节点
/**
*
*/

View File

@ -6,15 +6,15 @@ import cn.iocoder.yudao.framework.common.core.KeyValue;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.common.util.spring.SpringUtils;
import cn.iocoder.yudao.framework.tenant.core.context.TenantContextHolder;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.enums.definition.BpmHttpRequestParamTypeEnum;
import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
import com.fasterxml.jackson.core.type.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.runtime.ProcessInstance;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpMethod;
import org.springframework.http.ResponseEntity;
import org.springframework.http.*;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.client.RestClientException;
@ -26,7 +26,7 @@ import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.web.core.util.WebFrameworkUtils.HEADER_TENANT_ID;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR;
import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.PROCESS_INSTANCE_HTTP_CALL_ERROR;
/**
* HTTP
@ -42,7 +42,6 @@ public class BpmHttpRequestUtils {
List<BpmSimpleModelNodeVO.HttpRequestParam> bodyParams,
Boolean handleResponse,
List<KeyValue<String, String>> response) {
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
// 1.1 设置请求头
@ -51,6 +50,7 @@ public class BpmHttpRequestUtils {
MultiValueMap<String, String> body = buildHttpBody(processInstance, bodyParams);
// 2. 发起请求
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
ResponseEntity<String> responseEntity = sendHttpRequest(url, headers, body, restTemplate);
// 3. 处理返回
@ -78,27 +78,55 @@ public class BpmHttpRequestUtils {
}
}
public static void executeBpmHttpRequest(BpmProcessInstanceStatusEvent event,
String url) {
// 1.1 设置请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
if (TenantContextHolder.getTenantId() != null) {
headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
} else {
BpmProcessInstanceService processInstanceService = SpringUtils.getBean(BpmProcessInstanceService.class);
ProcessInstance processInstance = processInstanceService.getProcessInstance(event.getId());
if (processInstance != null) {
headers.add(HEADER_TENANT_ID, String.valueOf(TenantContextHolder.getTenantId()));
}
}
// 1.2 设置请求体
// MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
// body.add("id", event.getId());
// body.add("processDefinitionKey", event.getProcessDefinitionKey());
// body.add("status", event.getStatus().toString());
// if (StrUtil.isNotEmpty(event.getBusinessKey())) {
// body.add("businessKey", event.getBusinessKey());
// }
// 2. 发起请求
RestTemplate restTemplate = SpringUtils.getBean(RestTemplate.class);
sendHttpRequest(url, headers, event, restTemplate);
}
public static ResponseEntity<String> sendHttpRequest(String url,
MultiValueMap<String, String> headers,
MultiValueMap<String, String> body,
Object body,
RestTemplate restTemplate) {
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, headers);
HttpEntity<Object> requestEntity = new HttpEntity<>(body, headers);
ResponseEntity<String> responseEntity;
try {
responseEntity = restTemplate.exchange(url, HttpMethod.POST, requestEntity, String.class);
log.info("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
log.info("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},响应结果:{}]", headers, body, responseEntity);
} catch (RestClientException e) {
log.error("[sendHttpRequest][HTTP 触发器,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_TRIGGER_CALL_ERROR);
log.error("[sendHttpRequest][HTTP 请求,请求头:{},请求体:{},请求出错:{}]", headers, body, e.getMessage());
throw exception(PROCESS_INSTANCE_HTTP_CALL_ERROR);
}
return responseEntity;
}
public static MultiValueMap<String, String> buildHttpHeaders(ProcessInstance processInstance,
List<BpmSimpleModelNodeVO.HttpRequestParam> headerSettings) {
Map<String, Object> processVariables = processInstance.getProcessVariables();
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>();
headers.add(HEADER_TENANT_ID, processInstance.getTenantId());
Map<String, Object> processVariables = processInstance.getProcessVariables();
addHttpRequestParam(headers, headerSettings, processVariables);
return headers;
}

View File

@ -797,12 +797,13 @@ public class BpmnModelUtils {
// 情况StartEvent/EndEvent/UserTask/ServiceTask
if (currentElement instanceof StartEvent
|| currentElement instanceof EndEvent
|| currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) {
// 添加元素
|| currentElement instanceof EndEvent
|| currentElement instanceof UserTask
|| currentElement instanceof ServiceTask) {
// 添加节点
FlowNode flowNode = (FlowNode) currentElement;
resultElements.add(flowNode);
// 遍历子节点
flowNode.getOutgoingFlows().forEach(
nextElement -> simulateNextFlowElements(nextElement.getTargetFlowElement(), variables, resultElements, visitElements));
@ -835,6 +836,31 @@ public class BpmnModelUtils {
}
}
/**
*
*
* @param flowNode
* @param variables
*/
public static boolean isSkipNode(FlowElement flowNode, Map<String, Object> variables) {
// 1. 检查节点是否有跳过表达式(支持多种任务节点类型)
String skipExpression = null;
if (flowNode instanceof UserTask) {
skipExpression = ((UserTask) flowNode).getSkipExpression();
} else if (flowNode instanceof ServiceTask) {
skipExpression = ((ServiceTask) flowNode).getSkipExpression();
} else if (flowNode instanceof ScriptTask) {
skipExpression = ((ScriptTask) flowNode).getSkipExpression();
}
if (StrUtil.isEmpty(skipExpression)) {
return false;
}
// 2. 计算跳过表达式的值
return evalConditionExpress(variables, skipExpression);
}
/**
*
*
@ -912,8 +938,8 @@ public class BpmnModelUtils {
*/
private static SequenceFlow findMatchSequenceFlowByExclusiveGateway(Gateway gateway, Map<String, Object> variables) {
SequenceFlow matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
flow -> ObjUtil.notEqual(gateway.getDefaultFlow(), flow.getId())
&& (evalConditionExpress(variables, flow.getConditionExpression())));
if (matchSequenceFlow == null) {
matchSequenceFlow = CollUtil.findOne(gateway.getOutgoingFlows(),
flow -> ObjUtil.equal(gateway.getDefaultFlow(), flow.getId()));
@ -997,7 +1023,7 @@ public class BpmnModelUtils {
* @return
*/
public static boolean evalConditionExpress(Map<String, Object> variables, String expression) {
if (expression == null) {
if (StrUtil.isEmpty(expression)) {
return Boolean.FALSE;
}
// 如果 variables 为空,则创建一个的原因?可能 expression 的计算,不依赖于 variables

View File

@ -4,7 +4,6 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.*;
import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO;
import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.model.simple.BpmSimpleModelNodeVO.ConditionGroups;
@ -17,13 +16,14 @@ import cn.iocoder.yudao.module.bpm.service.task.listener.BpmCallActivityListener
import cn.iocoder.yudao.module.bpm.service.task.listener.BpmUserTaskListener;
import org.flowable.bpmn.BpmnAutoLayout;
import org.flowable.bpmn.constants.BpmnXMLConstants;
import org.flowable.bpmn.model.Process;
import org.flowable.bpmn.model.*;
import org.flowable.bpmn.model.Process;
import org.flowable.engine.delegate.ExecutionListener;
import org.flowable.engine.delegate.TaskListener;
import java.util.*;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.enums.BpmnModelConstants.*;
import static cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils.*;
import static java.util.Arrays.asList;
@ -239,13 +239,13 @@ public class SimpleModelUtils {
// 3.1 分支有后续节点。即分支 1: A->B->C->D 的情况
if (isValidNode(conditionChildNode)) {
// 3.1.1 建立与后续的节点的连线。例如说,建立 A->B 的连线
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), item);
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), conditionChildNode.getId(), nodeType, item);
process.addFlowElement(sequenceFlow);
// 3.1.2 递归调用后续节点连线。例如说,建立 B->C->D 的连线
traverseNodeToBuildSequenceFlow(process, conditionChildNode, branchEndNodeId);
} else {
// 3.2 分支没有后续节点。例如说,建立 A->D 的连线
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, item);
SequenceFlow sequenceFlow = ConditionNodeConvert.buildSequenceFlow(node.getId(), branchEndNodeId, nodeType, item);
process.addFlowElement(sequenceFlow);
}
}
@ -464,6 +464,10 @@ public class SimpleModelUtils {
addReasonRequire(node.getReasonRequire(), userTask);
// 节点类型
addNodeType(node.getType(), userTask);
// 添加跳过表达式
if (StrUtil.isNotEmpty(node.getSkipExpression())) {
userTask.setSkipExpression(node.getSkipExpression());
}
return userTask;
}
@ -591,17 +595,23 @@ public class SimpleModelUtils {
private static class ParallelBranchNodeConvert implements NodeConvert {
/**
* 使 true https://t.zsxq.com/m6GXh 反馈问题
*
* @see {@link ConditionNodeConvert#buildSequenceFlow}
*/
@Override
public List<ParallelGateway> convertList(BpmSimpleModelNodeVO node) {
ParallelGateway parallelGateway = new ParallelGateway();
parallelGateway.setId(node.getId());
public List<InclusiveGateway> convertList(BpmSimpleModelNodeVO node) {
InclusiveGateway inclusiveGateway = new InclusiveGateway();
inclusiveGateway.setId(node.getId());
// TODO @jasonsetName
// 行聚合网关由程序创建,前端不需要传入
ParallelGateway joinParallelGateway = new ParallelGateway();
// 并网关 由程序创建,前端不需要传入
InclusiveGateway joinParallelGateway = new InclusiveGateway();
joinParallelGateway.setId(buildGatewayJoinId(node.getId()));
// TODO @jasonsetName
return CollUtil.newArrayList(parallelGateway, joinParallelGateway);
return CollUtil.newArrayList(inclusiveGateway, joinParallelGateway);
}
@Override
@ -652,8 +662,14 @@ public class SimpleModelUtils {
}
public static SequenceFlow buildSequenceFlow(String sourceId, String targetId,
BpmSimpleModelNodeVO node) {
String conditionExpression = buildConditionExpression(node.getConditionSetting());
BpmSimpleModelNodeTypeEnum nodeType, BpmSimpleModelNodeVO node) {
String conditionExpression;
// 并行分支,使用包容网关实现,强制设置条件表达式为 true
if (BpmSimpleModelNodeTypeEnum.PARALLEL_BRANCH_NODE == nodeType) {
conditionExpression ="${true}";
} else {
conditionExpression = buildConditionExpression(node.getConditionSetting());
}
return buildBpmnSequenceFlow(sourceId, targetId, node.getId(), node.getName(), conditionExpression);
}
}
@ -662,7 +678,6 @@ public class SimpleModelUtils {
*
*/
public static String buildConditionExpression(BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
// 并行网关不需要设置条件
if (conditionSetting == null) {
return null;
}
@ -684,15 +699,18 @@ public class SimpleModelUtils {
if (conditionGroups == null || CollUtil.isEmpty(conditionGroups.getConditions())) {
return null;
}
List<String> strConditionGroups = CollectionUtils.convertList(conditionGroups.getConditions(), item -> {
List<String> strConditionGroups = convertList(conditionGroups.getConditions(), item -> {
if (CollUtil.isEmpty(item.getRules())) {
return "";
}
// 构造规则表达式
List<String> list = CollectionUtils.convertList(item.getRules(), (rule) -> {
List<String> list = convertList(item.getRules(), (rule) -> {
String rightSide = NumberUtil.isNumber(rule.getRightSide()) ? rule.getRightSide()
: "\"" + rule.getRightSide() + "\""; // 如果非数值类型加引号
return String.format(" %s %s var:convertByType(%s,%s)", rule.getLeftSide(), rule.getOpCode(), rule.getLeftSide(), rightSide);
return String.format(" vars:getOrDefault(%s, null) %s var:convertByType(%s,%s) ",
rule.getLeftSide(), // 左侧:读取变量
rule.getOpCode(), // 中间:操作符,比较
rule.getLeftSide(), rightSide); // 右侧转换变量VariableConvertByTypeExpressionFunction
});
// 构造条件组的表达式
Boolean and = item.getAnd();
@ -954,7 +972,7 @@ public class SimpleModelUtils {
|| nodeType == BpmSimpleModelNodeTypeEnum.COPY_NODE
|| nodeType == BpmSimpleModelNodeTypeEnum.CHILD_PROCESS
|| nodeType == BpmSimpleModelNodeTypeEnum.END_NODE) {
// 添加元素
// 添加此节点
resultNodes.add(currentNode);
}
@ -1000,6 +1018,16 @@ public class SimpleModelUtils {
simulateNextNode(currentNode.getChildNode(), variables, resultNodes);
}
/**
*
*/
public static boolean isSkipNode(BpmSimpleModelNodeVO currentNode, Map<String, Object> variables) {
if (StrUtil.isEmpty(currentNode.getSkipExpression())) {
return false;
}
return BpmnModelUtils.evalConditionExpress(variables, currentNode.getSkipExpression());
}
public static boolean evalConditionExpress(Map<String, Object> variables, BpmSimpleModelNodeVO.ConditionSetting conditionSetting) {
return BpmnModelUtils.evalConditionExpress(variables, buildConditionExpression(conditionSetting));
}

View File

@ -1,5 +1,7 @@
package cn.iocoder.yudao.module.bpm.framework.rpc.config;
import cn.iocoder.yudao.module.bpm.api.event.CrmContractStatusListener;
import cn.iocoder.yudao.module.bpm.api.event.CrmReceivableStatusListener;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.PostApi;
import cn.iocoder.yudao.module.system.api.dict.DictDataApi;
@ -7,11 +9,28 @@ import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.permission.RoleApi;
import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration(value = "bpmRpcConfiguration", proxyBeanMethods = false)
@EnableFeignClients(clients = {RoleApi.class, DeptApi.class, PostApi.class, AdminUserApi.class, SmsSendApi.class, DictDataApi.class,
PermissionApi.class})
public class RpcConfiguration {
// ========== 特殊:解决微 yudao-cloud 微服务场景下,跨服务(进程)无法 Listener 的问题 ==========
@Bean
@ConditionalOnMissingBean(name = "crmReceivableStatusListener")
public CrmReceivableStatusListener crmReceivableStatusListener() {
return new CrmReceivableStatusListener();
}
@Bean
@ConditionalOnMissingBean(name = "crmContractStatusListener")
public CrmContractStatusListener crmContractStatusListener() {
return new CrmContractStatusListener();
}
}

View File

@ -398,7 +398,7 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
? BpmSimpleModelNodeTypeEnum.START_USER_NODE.getType()
: ObjUtil.defaultIfNull(parseNodeType(flowNode), // 目的:解决“办理节点”的识别
BpmSimpleModelNodeTypeEnum.APPROVE_NODE.getType()))
.setStatus(FlowableUtils.getTaskStatus(task))
.setStatus(getEndActivityNodeStatus(task))
.setCandidateStrategy(BpmnModelUtils.parseCandidateStrategy(flowNode))
.setStartTime(DateUtils.of(task.getCreateTime())).setEndTime(DateUtils.of(task.getEndTime()))
.setTasks(singletonList(BpmProcessInstanceConvert.INSTANCE.buildApprovalTaskInfo(task)));
@ -462,6 +462,18 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
return approvalNodes;
}
/**
*
*/
private Integer getEndActivityNodeStatus(HistoricTaskInstance task) {
Integer status = FlowableUtils.getTaskStatus(task);
if (status != null) {
return status;
}
// 结束节点未获取到状态,为跳过状态。可见 bpmn 或者 simple 的 skipExpression
return BpmTaskStatusEnum.SKIP.getStatus();
}
/**
*
*/
@ -565,10 +577,14 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
if (runActivityIds.contains(node.getId())) {
return null;
}
Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
// 如果节点被跳过。设置状态为跳过
if (SimpleModelUtils.isSkipNode(node, processVariables)) {
status = BpmTaskStatusEnum.SKIP.getStatus();
}
ActivityNode activityNode = new ActivityNode().setId(node.getId()).setName(node.getName())
.setNodeType(node.getType()).setCandidateStrategy(node.getCandidateStrategy())
.setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
.setStatus(status);
// 1. 开始节点/审批节点
if (ObjectUtils.equalsAny(node.getType(),
@ -608,8 +624,13 @@ public class BpmProcessInstanceServiceImpl implements BpmProcessInstanceService
if (runActivityIds.contains(node.getId())) {
return null;
}
Integer status = BpmTaskStatusEnum.NOT_START.getStatus();
// 如果节点被跳过,状态设置为跳过
if(BpmnModelUtils.isSkipNode(node, processVariables)){
status = BpmTaskStatusEnum.SKIP.getStatus();
}
ActivityNode activityNode = new ActivityNode().setId(node.getId())
.setStatus(BpmTaskStatusEnum.NOT_START.getStatus());
.setStatus(status);
// 1. 开始节点
if (node instanceof StartEvent) {

View File

@ -947,7 +947,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
BpmCommentTypeEnum.DELEGATE_START.formatComment(currentUser.getNickname(), delegateUser.getNickname(), reqVO.getReason()));
// 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
taskService.setOwner(taskId, task.getAssignee());
// 特殊如果已经被委派owner 非空),则不需要更新 ownerhttps://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
if (StrUtil.isEmpty(task.getOwner())) {
taskService.setOwner(taskId, task.getAssignee());
}
// 3.2 执行委派,将任务委派给 delegateUser
taskService.delegateTask(taskId, reqVO.getDelegateUserId().toString());
// 补充说明:委托不单独设置状态。如果需要,可通过 Task 的 DelegationState 字段,判断是否为 DelegationState.PENDING 委托中
@ -973,7 +976,10 @@ public class BpmTaskServiceImpl implements BpmTaskService {
BpmCommentTypeEnum.TRANSFER.formatComment(currentUser.getNickname(), assigneeUser.getNickname(), reqVO.getReason()));
// 3.1 设置任务所有人 (owner) 为原任务的处理人 (assignee)
taskService.setOwner(taskId, task.getAssignee());
// 特殊如果已经被转派owner 非空),则不需要更新 ownerhttps://gitee.com/zhijiantianya/yudao-cloud/issues/ICJ153
if (StrUtil.isEmpty(task.getOwner())) {
taskService.setOwner(taskId, task.getAssignee());
}
// 3.2 执行转派(审批人),将任务转派给 assigneeUser
// 委托( delegate和转派transfer的差别就在这块的调用
taskService.setAssignee(taskId, reqVO.getAssigneeUserId().toString());

View File

@ -2,20 +2,28 @@ package cn.iocoder.yudao.module.crm.service.contract.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.crm.enums.ApiConstants;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractService;
import cn.iocoder.yudao.module.crm.service.contract.CrmContractServiceImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
*
*
* @author HUIHUI
*/
@Component
@RestController
@Validated
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
public class CrmContractStatusListener extends BpmProcessInstanceStatusEventListener {
private static final String PREFIX = ApiConstants.PREFIX + "/contract";
@Resource
private CrmContractService contractService;
@ -25,7 +33,8 @@ public class CrmContractStatusListener extends BpmProcessInstanceStatusEventList
}
@Override
protected void onEvent(BpmProcessInstanceStatusEvent event) {
@PostMapping(PREFIX + "/update-audit-status") // 目的:提供给 bpm-server rpc 调用
protected void onEvent(@RequestBody BpmProcessInstanceStatusEvent event) {
contractService.updateContractAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
}

View File

@ -1,21 +1,30 @@
package cn.iocoder.yudao.module.crm.service.receivable.listener;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.crm.enums.ApiConstants;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableService;
import cn.iocoder.yudao.module.crm.service.receivable.CrmReceivableServiceImpl;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import jakarta.annotation.Resource;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
/**
*
*
* @author HUIHUI
*/
@Component
@RestController
@Validated
@FeignClient(name = ApiConstants.NAME) // TODO 芋艿fallbackFactory =
public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventListener {
private static final String PREFIX = ApiConstants.PREFIX + "/receivable";
@Resource
private CrmReceivableService receivableService;
@ -25,7 +34,8 @@ public class CrmReceivableStatusListener extends BpmProcessInstanceStatusEventLi
}
@Override
public void onEvent(BpmProcessInstanceStatusEvent event) {
@PostMapping(PREFIX + "/update-audit-status") // 目的:提供给 bpm-server rpc 调用
public void onEvent(@RequestBody BpmProcessInstanceStatusEvent event) {
receivableService.updateReceivableAuditStatus(Long.parseLong(event.getBusinessKey()), event.getStatus());
}