|
@@ -57,9 +57,11 @@ import java.io.File;
|
|
|
import java.time.LocalDateTime;
|
|
import java.time.LocalDateTime;
|
|
|
import java.time.temporal.ChronoUnit;
|
|
import java.time.temporal.ChronoUnit;
|
|
|
import java.util.Collections;
|
|
import java.util.Collections;
|
|
|
|
|
+import java.util.LinkedHashSet;
|
|
|
import java.util.List;
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
import java.util.Map;
|
|
|
import java.util.Objects;
|
|
import java.util.Objects;
|
|
|
|
|
+import java.util.Set;
|
|
|
import java.util.stream.Collectors;
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
|
|
import static com.qqflow.engine.common.constant.SecurityConstants.ASSIGNEE_TYPE_ROLE;
|
|
import static com.qqflow.engine.common.constant.SecurityConstants.ASSIGNEE_TYPE_ROLE;
|
|
@@ -269,7 +271,7 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
|
|
|
ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
|
|
ProcessInstance instance = this.getActiveInstance(task.getInstanceId());
|
|
|
Long operatorId = SecurityUtils.getUserId();
|
|
Long operatorId = SecurityUtils.getUserId();
|
|
|
Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
|
|
Long transferToUserId = dto.getTransferToUserId() != null ? dto.getTransferToUserId() : dto.getTransferTo();
|
|
|
- this.validateTransferTarget(transferToUserId, operatorId);
|
|
|
|
|
|
|
+ this.validateTransferTarget(transferToUserId, operatorId, instance);
|
|
|
this.saveRecord(task, instance, operatorId, ApprovalAction.TRANSFER, ApprovalResult.TRANSFER.getCode(), dto.getComment(), null);
|
|
this.saveRecord(task, instance, operatorId, ApprovalAction.TRANSFER, ApprovalResult.TRANSFER.getCode(), dto.getComment(), null);
|
|
|
this.updateTaskToTransferred(task);
|
|
this.updateTaskToTransferred(task);
|
|
|
this.createTransferTask(task, instance, transferToUserId);
|
|
this.createTransferTask(task, instance, transferToUserId);
|
|
@@ -284,20 +286,20 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
|
|
|
if (assigneeId == null) {
|
|
if (assigneeId == null) {
|
|
|
throw new BusinessException("加签人不能为空");
|
|
throw new BusinessException("加签人不能为空");
|
|
|
}
|
|
}
|
|
|
- this.validateTransferTarget(assigneeId, null);
|
|
|
|
|
|
|
+ ProcessInstance instance = this.processInstanceMapper.selectById(task.getInstanceId());
|
|
|
|
|
+ this.validateTransferTarget(assigneeId, null, instance);
|
|
|
ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
|
|
ApprovalTask newTask = this.approvalTaskAssembler.buildNew(
|
|
|
task.getInstanceId(), task.getNodeId(), task.getNodeName(),
|
|
task.getInstanceId(), task.getNodeId(), task.getNodeName(),
|
|
|
task.getNodeType(), assigneeId, ASSIGNEE_TYPE_USER, TaskStatus.PENDING.getCode()
|
|
task.getNodeType(), assigneeId, ASSIGNEE_TYPE_USER, TaskStatus.PENDING.getCode()
|
|
|
);
|
|
);
|
|
|
this.approvalTaskMapper.insert(newTask);
|
|
this.approvalTaskMapper.insert(newTask);
|
|
|
- ProcessInstance instance = this.processInstanceMapper.selectById(task.getInstanceId());
|
|
|
|
|
Long operatorId = SecurityUtils.getUserId();
|
|
Long operatorId = SecurityUtils.getUserId();
|
|
|
this.saveRecord(task, instance, operatorId, ApprovalAction.ADD_SIGN, ApprovalResult.PASS.getCode(), null, null);
|
|
this.saveRecord(task, instance, operatorId, ApprovalAction.ADD_SIGN, ApprovalResult.PASS.getCode(), null, null);
|
|
|
this.publishTaskCompletedEvent(task, instance, operatorId,
|
|
this.publishTaskCompletedEvent(task, instance, operatorId,
|
|
|
ApprovalAction.ADD_SIGN.getCode(), ApprovalResult.PASS.getCode());
|
|
ApprovalAction.ADD_SIGN.getCode(), ApprovalResult.PASS.getCode());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- private void validateTransferTarget(Long targetUserId, Long operatorId) {
|
|
|
|
|
|
|
+ private void validateTransferTarget(Long targetUserId, Long operatorId, ProcessInstance instance) {
|
|
|
if (targetUserId == null) {
|
|
if (targetUserId == null) {
|
|
|
throw new BusinessException("转办/加签人不能为空");
|
|
throw new BusinessException("转办/加签人不能为空");
|
|
|
}
|
|
}
|
|
@@ -311,6 +313,12 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
|
|
|
if (targetUser.getStatus() != null && targetUser.getStatus() != 0) {
|
|
if (targetUser.getStatus() != null && targetUser.getStatus() != 0) {
|
|
|
throw new BusinessException("转办/加签人已被禁用");
|
|
throw new BusinessException("转办/加签人已被禁用");
|
|
|
}
|
|
}
|
|
|
|
|
+ // 外部系统发起的流程(如优惠券审批)只允许在本流程审批人范围内转办/加签,
|
|
|
|
|
+ // 防止绕开选人接口直接指定 QQFlow 内部或其他商户人员
|
|
|
|
|
+ if (instance != null && this.isExternalInstance(instance)
|
|
|
|
|
+ && !this.resolveInstanceApproverIds(instance).contains(targetUserId)) {
|
|
|
|
|
+ throw new BusinessException("只能转办/加签给本流程的审批人");
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
@Override
|
|
@Override
|
|
@@ -683,6 +691,11 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
|
|
|
if (instance == null) {
|
|
if (instance == null) {
|
|
|
throw new BusinessException("流程实例不存在");
|
|
throw new BusinessException("流程实例不存在");
|
|
|
}
|
|
}
|
|
|
|
|
+ // 外部系统发起的流程(如优惠券审批):候选池收敛到本实例的审批人,
|
|
|
|
|
+ // 避免把 QQFlow 内部人员、其他商户审批人暴露给外部审批方
|
|
|
|
|
+ if (this.isExternalInstance(instance)) {
|
|
|
|
|
+ return this.listInstanceApprovers(instance, operatorId);
|
|
|
|
|
+ }
|
|
|
ProcessDefinition definition = this.processDefinitionMapper.selectById(instance.getProcessDefinitionId());
|
|
ProcessDefinition definition = this.processDefinitionMapper.selectById(instance.getProcessDefinitionId());
|
|
|
if (definition == null || definition.getModelJson() == null) {
|
|
if (definition == null || definition.getModelJson() == null) {
|
|
|
return this.listAllActiveSystemUsers(operatorId);
|
|
return this.listAllActiveSystemUsers(operatorId);
|
|
@@ -763,4 +776,70 @@ public class ApprovalTaskServiceImpl implements ApprovalTaskService {
|
|
|
.map(UserDTO::of)
|
|
.map(UserDTO::of)
|
|
|
.collect(Collectors.toList());
|
|
.collect(Collectors.toList());
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 是否外部系统发起的流程:发起时由外部系统指定了各节点审批人(nodeAssignees)。
|
|
|
|
|
+ * <p>目前仅 wl-scrm 优惠券审批使用该字段,用于对这类流程做人员范围收敛。</p>
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isExternalInstance(ProcessInstance instance) {
|
|
|
|
|
+ return StringUtils.hasText(instance.getNodeAssignees());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 流程实例内的审批人集合:发起时配置的各节点审批人 + 本实例已产生任务的接收人。
|
|
|
|
|
+ * <p>带上任务接收人是为了保证被转办/加签的人仍能在流程内继续流转。</p>
|
|
|
|
|
+ */
|
|
|
|
|
+ private Set<Long> resolveInstanceApproverIds(ProcessInstance instance) {
|
|
|
|
|
+ Set<Long> userIds = new LinkedHashSet<>();
|
|
|
|
|
+ if (StringUtils.hasText(instance.getNodeAssignees())) {
|
|
|
|
|
+ try {
|
|
|
|
|
+ Map<String, List<Long>> assignees = this.objectMapper.readValue(
|
|
|
|
|
+ instance.getNodeAssignees(), new TypeReference<Map<String, List<Long>>>() {
|
|
|
|
|
+ });
|
|
|
|
|
+ assignees.values().stream()
|
|
|
|
|
+ .filter(Objects::nonNull)
|
|
|
|
|
+ .forEach(userIds::addAll);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ throw new BusinessException("解析外部指定审批人失败:" + e.getMessage());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ List<ApprovalTask> tasks = this.approvalTaskMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<ApprovalTask>()
|
|
|
|
|
+ .eq(ApprovalTask::getInstanceId, instance.getId()));
|
|
|
|
|
+ for (ApprovalTask task : tasks) {
|
|
|
|
|
+ // ROLE 任务的 assigneeId 是角色ID,不能当作用户ID
|
|
|
|
|
+ if (ASSIGNEE_TYPE_USER.equals(task.getAssigneeType()) && task.getAssigneeId() != null) {
|
|
|
|
|
+ userIds.add(task.getAssigneeId());
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ return userIds;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 本流程审批人列表:仅返回选人必要字段,不暴露手机号、邮箱等敏感信息。
|
|
|
|
|
+ */
|
|
|
|
|
+ private List<UserDTO> listInstanceApprovers(ProcessInstance instance, Long excludeUserId) {
|
|
|
|
|
+ Set<Long> userIds = this.resolveInstanceApproverIds(instance);
|
|
|
|
|
+ if (userIds.isEmpty()) {
|
|
|
|
|
+ return Collections.emptyList();
|
|
|
|
|
+ }
|
|
|
|
|
+ List<SysUser> users = this.sysUserMapper.selectList(
|
|
|
|
|
+ new LambdaQueryWrapper<SysUser>()
|
|
|
|
|
+ .in(SysUser::getId, userIds)
|
|
|
|
|
+ .eq(SysUser::getStatus, 0)
|
|
|
|
|
+ .orderByAsc(SysUser::getRealName, SysUser::getId));
|
|
|
|
|
+ return users.stream()
|
|
|
|
|
+ .filter(u -> excludeUserId == null || !u.getId().equals(excludeUserId))
|
|
|
|
|
+ .map(this::buildPickerUser)
|
|
|
|
|
+ .collect(Collectors.toList());
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ private UserDTO buildPickerUser(SysUser user) {
|
|
|
|
|
+ UserDTO dto = new UserDTO();
|
|
|
|
|
+ dto.setId(user.getId());
|
|
|
|
|
+ dto.setUsername(user.getUsername());
|
|
|
|
|
+ dto.setRealName(user.getRealName());
|
|
|
|
|
+ dto.setDeptId(user.getDeptId());
|
|
|
|
|
+ return dto;
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|