splitUsernames(Object value) {
- if (value == null) {
- return Collections.emptyList();
- }
- return Arrays.stream(String.valueOf(value).split(SymbolConstant.COMMA))
- .map(String::trim)
- .filter(org.apache.commons.lang.StringUtils::isNotBlank)
- .collect(Collectors.toList());
- }
-
}
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java
index 75c6673..5d8f15f 100644
--- a/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/java/org/jeecg/xispeakfb/XiSpeakFeedbackFlow.java
@@ -115,6 +115,56 @@ public class XiSpeakFeedbackFlow {
.orElse("");
}
+ // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplDeptIsEnd(execution)}
+ public Integer getImplDeptIsEnd(DelegateExecution execution) {
+ DeptApproveDetail detail = getDeptApproveDetailByTask(execution);
+ if (detail == null || detail.getIsEnd() == null) {
+ return 0;
+ }
+ return detail.getIsEnd();
+ }
+
+ // 流程表达式内用法 ${XiSpeakFeedbackFlow.getImplLeaderAsWorker(execution)}
+ public String getImplLeaderAsWorker(DelegateExecution execution) {
+ DeptApproveDetail detail = getDeptApproveDetailByTask(execution);
+ if (detail == null || StringUtils.isBlank(detail.getImplWorker())) {
+ return StringUtils.EMPTY;
+ }
+ return detail.getImplWorker();
+ }
+
+ /**
+ * 从 execution 获取 businessKey → TaskTask → BgXiSpeak → DeptApproveDetail
+ */
+ private DeptApproveDetail getDeptApproveDetailByTask(DelegateExecution execution) {
+ String businessKey = execution.getProcessInstanceBusinessKey();
+
+ if (StringUtils.isBlank(businessKey)) {
+ log.warn("流程 BusinessKey 缺失,ExecutionId: {}", execution.getId());
+ return null;
+ }
+
+ TaskTask taskTask = taskTaskService.getById(businessKey);
+ if (taskTask == null || StringUtils.isBlank(taskTask.getBusinessId())) {
+ log.warn("无法获取任务数据或业务关联ID![taskTaskId: {}]", businessKey);
+ return null;
+ }
+
+ BgXiSpeak xiSpeak = bgXiSpeakService.getById(taskTask.getBusinessId());
+ if (xiSpeak == null || xiSpeak.getApproveInfo() == null) {
+ log.warn("业务表单数据或审批配置缺失![xiBusinessId: {}]", taskTask.getBusinessId());
+ return null;
+ }
+
+ String deptId = taskTask.getDeptId();
+ if (StringUtils.isBlank(deptId)) {
+ log.warn("中间表表单部门信息缺失![tasktaskId: {}]", taskTask.getId());
+ return null;
+ }
+
+ return xiSpeak.getApproveInfo().get(deptId);
+ }
+
/**
* 获取当前流程的经办部门的经办人。
* 用于流程表达式判断,通过 BusinessKey 关联业务主键,获取当前经办人。
diff --git a/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml b/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
index 3430518..dc72fdd 100644
--- a/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
+++ b/jeecg-boot-module/jeecg-module-flow/src/main/resources/application-flow.yml
@@ -17,6 +17,7 @@ flow-biz:
tem-impl-leader-key: "tem_impl_leader"
tem-bg-leader-key: "tem_bg_leader"
sdw-leader-list-key: "sdwLeaderList"
+ is-end-key: "isEnd"
xi-speak-fb:
dept-id: "x"
business-id: "business_id"
diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActProcessController.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActProcessController.java
index 5cdcfc2..9febc34 100644
--- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActProcessController.java
+++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/controller/ActProcessController.java
@@ -19,6 +19,7 @@ import org.jeecg.common.util.RedisUtil;
import org.jeecg.common.util.TokenUtils;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.modules.bpm.dto.ProcessVariableDTO;
+import org.jeecg.modules.bpm.service.BpmVariableLocalService;
import org.jeecg.modules.bpm.service.impl.ActProcessService;
import org.jeecg.modules.extbpm.process.service.IExtActProcessService;
import org.jeecg.modules.extbpm.util.CommonRandomUtil;
@@ -57,6 +58,8 @@ public class ActProcessController {
@Autowired
private TaskService taskService;
@Autowired
+ private BpmVariableLocalService bpmVariableLocalService;
+ @Autowired
private ActProcessService actProcessService;
@Autowired
private IExtActProcessService extActProcessService;
@@ -115,6 +118,24 @@ public class ActProcessController {
return Result.OK("保存成功");
}
+ @PostMapping(value = "/setBpmVariableLocal")
+ @Operation(summary = "设置局部流程变量(向上追溯2层父级执行实例)")
+ public Result setBpmVariableLocal(@RequestBody ProcessVariableDTO dto) {
+ if (dto == null || oConvertUtils.isEmpty(dto.getTaskId())) {
+ return Result.error("任务ID不能为空");
+ }
+ if (oConvertUtils.isEmpty(dto.getVarField())) {
+ return Result.error("流程变量名不能为空");
+ }
+ try {
+ bpmVariableLocalService.setVariableLocal(dto.getTaskId(), dto.getVarField(), dto.getVarVal());
+ return Result.OK("保存成功");
+ } catch (Exception e) {
+ log.error("设置BPM局部变量失败", e);
+ return Result.error("保存失败: " + e.getMessage());
+ }
+ }
+
@PostMapping("deploy")
@Operation(summary = "部署流程文件")
public Result deploy(@RequestParam("processFile") MultipartFile file) throws IOException {
diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/BpmVariableLocalService.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/BpmVariableLocalService.java
new file mode 100644
index 0000000..bea3c35
--- /dev/null
+++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/BpmVariableLocalService.java
@@ -0,0 +1,13 @@
+package org.jeecg.modules.bpm.service;
+
+public interface BpmVariableLocalService {
+
+ /**
+ * 将变量存入向上追溯2层的父级执行实例中(与 AfterImplWorkerTemStoreListener 逻辑一致)
+ *
+ * @param taskId 当前任务ID
+ * @param varField 变量名
+ * @param varVal 变量值
+ */
+ void setVariableLocal(String taskId, String varField, Object varVal);
+}
diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/BpmVariableLocalServiceImpl.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/BpmVariableLocalServiceImpl.java
new file mode 100644
index 0000000..e8c6b7b
--- /dev/null
+++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/bpm/service/impl/BpmVariableLocalServiceImpl.java
@@ -0,0 +1,59 @@
+package org.jeecg.modules.bpm.service.impl;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.RuntimeService;
+import org.flowable.engine.TaskService;
+import org.flowable.engine.runtime.Execution;
+import org.flowable.task.api.Task;
+import org.jeecg.common.util.oConvertUtils;
+import org.jeecg.modules.bpm.service.BpmVariableLocalService;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+@Slf4j
+@Service
+@RequiredArgsConstructor(onConstructor_ = @Autowired)
+public class BpmVariableLocalServiceImpl implements BpmVariableLocalService {
+
+ private final RuntimeService runtimeService;
+ private final TaskService taskService;
+
+ @Override
+ public void setVariableLocal(String taskId, String varField, Object varVal) {
+ Task task = taskService.createTaskQuery().taskId(taskId).singleResult();
+ if (task == null) {
+ throw new IllegalArgumentException("任务ID不存在: " + taskId);
+ }
+
+ String executionId = task.getExecutionId();
+
+ Execution currentExecution = runtimeService.createExecutionQuery()
+ .executionId(executionId)
+ .singleResult();
+ if (currentExecution == null) {
+ runtimeService.setVariableLocal(executionId, varField, varVal);
+ return;
+ }
+
+ String parentId = currentExecution.getParentId();
+ if (oConvertUtils.isEmpty(parentId)) {
+ runtimeService.setVariableLocal(executionId, varField, varVal);
+ return;
+ }
+
+ Execution parentExecution = runtimeService.createExecutionQuery()
+ .executionId(parentId)
+ .singleResult();
+ if (parentExecution == null) {
+ runtimeService.setVariableLocal(parentId, varField, varVal);
+ return;
+ }
+
+ String grandParentId = parentExecution.getParentId();
+ String targetId = oConvertUtils.isNotEmpty(grandParentId) ? grandParentId : parentId;
+
+ runtimeService.setVariableLocal(targetId, varField, varVal);
+ log.info("BPM局部变量已存入: targetId={}, varField={}, varVal={}", targetId, varField, varVal);
+ }
+}
diff --git a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java
index d7344c7..f9a36ad 100644
--- a/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java
+++ b/jeecg-boot-platform/jeecg-boot-module-bpm-flowable/src/main/java/org/jeecg/modules/extbpm/process/common/expression/FlowNodeExpression.java
@@ -427,6 +427,43 @@ public class FlowNodeExpression {
return this.getSonProcessHqVariableList(execution,variableName).size();
}
+ /**
+ * 获取子流程原始变量值(不经过 splitUsernames 逗号分割),适用于非列表类型的变量。
+ * 遍历逻辑与 getSonProcessHqVariableList 一致。
+ */
+ public Object getSonProcessHqVariable(DelegateExecution execution, String variableName) {
+ if (execution == null || StringUtils.isBlank(variableName)) {
+ return null;
+ }
+ try {
+ String parentExecutionId = execution.getParentId();
+ if (StringUtils.isBlank(parentExecutionId)) {
+ return null;
+ }
+ if (execution.hasVariableLocal(variableName)) {
+ return execution.getVariableLocal(variableName);
+ }
+
+ List executions = runtimeService.createExecutionQuery()
+ .processInstanceId(parentExecutionId)
+ .list();
+ for (Execution childExecution : executions) {
+ if (execution.getId().equals(childExecution.getId())) {
+ continue;
+ }
+ Object localVariable = runtimeService.getVariableLocal(childExecution.getId(), variableName);
+ if (localVariable != null) {
+ return localVariable;
+ }
+ }
+ return execution.getVariable(variableName);
+ } catch (Exception e) {
+ log.warn("getSonProcessHqVariable failed, executionId={}, variableName={}",
+ execution.getId(), variableName, e);
+ return null;
+ }
+ }
+
public String getSonProcessHqVariableString(DelegateExecution execution, String variableName) {
if(execution == null || StringUtils.isBlank(variableName) || getSonProcessHqVariableList(execution, variableName).isEmpty()) return StringUtils.EMPTY;
return getSonProcessHqVariableList(execution, variableName).get(0);
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java
index bd686b4..fecc1df 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/controller/BgXiSpeakController.java
@@ -266,6 +266,26 @@ public class BgXiSpeakController extends JeecgController saveSubApprove(@RequestBody Map params) {
+ String mainId = (String) params.get("mainId");
+ String deptId = (String) params.get("deptId");
+ Integer isEnd = params.get("isEnd") != null ? Integer.valueOf(params.get("isEnd").toString()) : null;
+ String implWorker = (String) params.get("implWorker");
+ @SuppressWarnings("unchecked")
+ List implUserNameList = (List) params.get("implUserNameList");
+ if (oConvertUtils.isEmpty(mainId) || oConvertUtils.isEmpty(deptId)) {
+ return Result.error("主表ID和部门ID不能为空");
+ }
+ bgXiSpeakService.saveSubApprove(mainId, deptId, isEnd, implWorker, implUserNameList);
+ return Result.OK("保存成功");
+ }
+
/**
* 保存业务数据同时更新流程变量jsonData
* @param id
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java
index 1516e20..e067a2a 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/entity/DeptApproveDetail.java
@@ -14,6 +14,8 @@ public class DeptApproveDetail {
private String deptId; // 部门ID
private String approverId; // 审批人ID(从监听器获取的)
private String approverName; // 审批人姓名(可选)
+ private Integer isEnd; // 是否办结(0=分配部门经办人,1=办结)
+ private String implWorker; // 被分配的部门经办人用户名
@JsonAlias("implUserId")
private List implUserIdList; // 落实人Id
@JsonAlias("implUserName")
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java
index 0ff70f6..ffb6894 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/IBgXiSpeakService.java
@@ -85,4 +85,10 @@ public interface IBgXiSpeakService extends IService {
List getRootListWithDept(String pid);
void checkAndMarkCompletedByProcessInstId(String processInstanceId);
+
+ /**
+ * 保存部门审批信息(isEnd、部门经办人/经办人列表)到业务表单 approveInfo JSON 中
+ * isEnd=1 时 implWorker 生效,isEnd=0 时 implUserNameList 生效
+ */
+ void saveSubApprove(String mainId, String deptId, Integer isEnd, String implWorker, List implUserNameList);
}
diff --git a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
index 482686a..3156842 100644
--- a/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
+++ b/jeecg-module-supervision/src/main/java/org/jeecg/modules/bg/xispeak/service/impl/BgXiSpeakServiceImpl.java
@@ -8,6 +8,8 @@ import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.util.oConvertUtils;
import org.jeecg.common.system.vo.SelectTreeModel;
import org.jeecg.modules.bg.xispeak.entity.BgXiSpeak;
+import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetail;
+import org.jeecg.modules.bg.xispeak.entity.DeptApproveDetailMap;
import org.jeecg.modules.bg.xispeak.mapper.BgXiSpeakMapper;
import org.jeecg.modules.bg.xispeak.service.IBgXiSpeakService;
import org.jeecg.modules.taskapprovalopinion.entity.TaskApprovalOpinion;
@@ -351,4 +353,40 @@ public class BgXiSpeakServiceImpl extends ServiceImpl implUserNameList) {
+ BgXiSpeak xiSpeak = this.getById(mainId);
+ if (xiSpeak == null) {
+ throw new JeecgBootException("业务表单不存在");
+ }
+
+ DeptApproveDetailMap approveMap = xiSpeak.getApproveInfo();
+ if (approveMap == null) {
+ approveMap = new DeptApproveDetailMap();
+ }
+
+ DeptApproveDetail detail = approveMap.get(deptId);
+ if (detail == null) {
+ detail = new DeptApproveDetail();
+ detail.setDeptId(deptId);
+ detail.setImplUserNameList(new ArrayList<>());
+ approveMap.put(deptId, detail);
+ }
+
+ detail.setIsEnd(isEnd);
+ if (isEnd != null && isEnd == 1) {
+ detail.setImplWorker(implWorker);
+ detail.setImplUserNameList(null);
+ } else {
+ detail.setImplWorker(null);
+ detail.setImplUserNameList(implUserNameList != null ? implUserNameList : new ArrayList<>());
+ }
+
+ xiSpeak.setApproveInfo(approveMap);
+ this.updateById(xiSpeak);
+ log.info("部门审批信息保存成功 mainId={}, deptId={}, isEnd={}, implWorker={}, implUserNameList={}",
+ mainId, deptId, isEnd, implWorker, implUserNameList);
+ }
}