Commit 8476e93e by 王志超

feat: 主单审批,驳回选定的行动工单

parent c8fc33d3
Pipeline #86574 passed with stages
in 1 minute 28 seconds
......@@ -1656,8 +1656,23 @@ public class ChangeFlowBiz {
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:{}, 审批结果:通过",
flowId, node.getName(), nextNodeId);
} else {
// 审批不通过:回退到确认变更方案节点,并取消所有子单
// 审批不通过:回退到确认变更方案节点,并重置指定的子单
log.warn("[{}] 审批不通过,flowId:{}", node.getName(), flowId);
// 验证必须选择要驳回的子单
List<String> rejectSubFlowIds = changeFlowSubmitReq.getRejectSubFlowIds();
if (CollectionUtils.isEmpty(rejectSubFlowIds)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "审批不通过时必须选择要驳回的行动工单");
}
// 验证驳回的子单归属于当前主工单,并返回要驳回的子单列表
List<ChangeSubFlowRecord> subFlowsToReject = validateAndFilterSubFlowsToReject(changeRecord.getId(), rejectSubFlowIds);
String rejectReason = changeFlowSubmitReq.getRejectReason();
if (StringUtils.isBlank(rejectReason)) {
rejectReason = "主单审批不通过";
}
Map<String, Object> rejectParamMap = new HashMap<>();
rejectParamMap.put("type", FlowTransitionType.TYPE_BACK_TO_CONFIRM_PLAN);
......@@ -1671,23 +1686,14 @@ public class ChangeFlowBiz {
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
changeFlowService.updateRecord(changeRecord);
// 取消指定的子单(审批人选择要取消的行动工单)
String rejectReason = changeFlowSubmitReq.getRejectReason();
if (StringUtils.isBlank(rejectReason)) {
rejectReason = "主单审批不通过";
}
List<String> cancelSubFlowIds = changeFlowSubmitReq.getCancelSubFlowIds();
int cancelledCount = 0;
if (CollectionUtils.isNotEmpty(cancelSubFlowIds)) {
cancelledCount = cancelSelectedSubFlows(changeRecord.getId(), cancelSubFlowIds, rejectReason);
}
// 重置被驳回的子单(已过滤,直接重置并流转工单)
int resetCount = resetRejectedSubFlows(subFlowsToReject, rejectReason);
// 发送审批不通过邮件通知
sendMainFlowRejectEmail(changeRecord, node.getName(), rejectReason, cancelledCount);
// sendMainFlowRejectEmail(changeRecord, node.getName(), rejectReason, resetCount);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:确认变更方案, 审批结果:不通过,已取消子单数:{}",
flowId, node.getName(), cancelledCount);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:确认变更方案, 审批结果:不通过,已驳回子单数:{}",
flowId, node.getName(), resetCount);
}
return nextNodeId;
......@@ -1781,14 +1787,64 @@ public class ChangeFlowBiz {
}
/**
* 验证子单归属于主工单(查询一次,返回所有子单供后续复用)
*
* @param changeRecordId 主工单ID
* @param subFlowIds 子单ID列表
* @return 主工单下所有子单列表
*/
/**
* 验证并过滤出要驳回的子单
*
* @param changeRecordId 主工单ID
* @param rejectSubFlowIds 要驳回的子单ID列表
* @return 过滤后的要驳回的子单列表
*/
private List<ChangeSubFlowRecord> validateAndFilterSubFlowsToReject(Long changeRecordId, List<String> rejectSubFlowIds) {
// 查询该主工单下所有子单(只查询一次)
List<ChangeSubFlowRecord> allSubFlows = changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
if (CollectionUtils.isEmpty(allSubFlows)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
"主工单下没有子单,无法驳回");
}
// 构建子单ID到记录的映射
Map<String, ChangeSubFlowRecord> subFlowMap = allSubFlows.stream()
.collect(Collectors.toMap(ChangeSubFlowRecord::getSubFlowId, subFlow -> subFlow));
// 检查要驳回的子单是否都存在
List<String> notFoundSubFlowIds = rejectSubFlowIds.stream()
.filter(subFlowId -> !subFlowMap.containsKey(subFlowId))
.collect(Collectors.toList());
if (CollectionUtils.isNotEmpty(notFoundSubFlowIds)) {
String notFoundIdsStr = String.join(",", notFoundSubFlowIds);
log.warn("[validateAndFilterSubFlowsToReject] 部分行动工单不存在或不归属于当前主工单, changeRecordId:{}, notFoundSubFlowIds:{}",
changeRecordId, notFoundIdsStr);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
"部分行动工单不存在或不归属于当前主工单:" + notFoundIdsStr);
}
// 过滤出要驳回的子单
List<ChangeSubFlowRecord> subFlowsToReject = rejectSubFlowIds.stream()
.map(subFlowMap::get)
.collect(Collectors.toList());
log.info("[validateAndFilterSubFlowsToReject] 子单归属校验通过, changeRecordId:{}, 总子单数:{}, 待驳回数:{}",
changeRecordId, allSubFlows.size(), subFlowsToReject.size());
return subFlowsToReject;
}
/**
* 发送主单审批不通过邮件通知
*
* @param changeRecord 主工单记录
* @param rejectNodeName 审批不通过的节点名称
* @param rejectReason 拒绝原因
* @param cancelledCount 取消的子单数量
* @param resetCount 重置的子单数量
*/
private void sendMainFlowRejectEmail(ChangeRecord changeRecord, String rejectNodeName, String rejectReason, int cancelledCount) {
private void sendMainFlowRejectEmail(ChangeRecord changeRecord, String rejectNodeName, String rejectReason, int resetCount) {
try {
// 构建邮件参数
Map<String, Object> emailParam = new HashMap<>();
......@@ -1805,7 +1861,7 @@ public class ChangeFlowBiz {
emailParam.put("changeContent", changeRecord.getChangeContent());
emailParam.put("rejectNodeName", rejectNodeName);
emailParam.put("rejectReason", rejectReason);
emailParam.put("cancelledSubFlowCount", cancelledCount);
emailParam.put("resetSubFlowCount", resetCount);
emailParam.put("flowUrl", changeRecord.getFlowId());
String subjectParam = changeRecord.getFlowId().toString();
......@@ -1856,95 +1912,88 @@ public class ChangeFlowBiz {
}
/**
* 取消主工单下指定的子单(主单审批不通过时调用)
* 重置被驳回的子单(主单审批不通过时调用)
* 将子单状态和节点重置到初始状态,保留流程记录
*
* @param changeRecordId 主工单ID
* @param cancelSubFlowIds 要取消的子单ID列表
* @param cancelReason 取消原因
* @return 实际取消的子单数量
* @param subFlowsToReject 要驳回的子单列表(已过滤,直接处理)
* @param rejectReason 驳回原因
* @return 实际重置的子单数量
*/
private int cancelSelectedSubFlows(Long changeRecordId, List<String> cancelSubFlowIds, String cancelReason) {
if (CollectionUtils.isEmpty(cancelSubFlowIds)) {
log.info("[cancelSelectedSubFlows] 未指定要取消的子单,changeRecordId:{}", changeRecordId);
private int resetRejectedSubFlows(List<ChangeSubFlowRecord> subFlowsToReject, String rejectReason) {
if (CollectionUtils.isEmpty(subFlowsToReject)) {
log.info("[resetRejectedSubFlows] 未指定要驳回的子单");
return 0;
}
// 查询该主工单下所有子单
List<ChangeSubFlowRecord> allSubFlows = changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
if (CollectionUtils.isEmpty(allSubFlows)) {
log.info("[cancelSelectedSubFlows] 主工单没有子单,changeRecordId:{}", changeRecordId);
return 0;
}
log.info("[resetRejectedSubFlows] 开始重置被驳回的子单,待重置数量:{}, 驳回原因:{}",
subFlowsToReject.size(), rejectReason);
// 过滤出要取消的子单
List<ChangeSubFlowRecord> subFlowsToCancel = allSubFlows.stream()
.filter(subFlow -> cancelSubFlowIds.contains(subFlow.getSubFlowId()))
.collect(Collectors.toList());
if (CollectionUtils.isEmpty(subFlowsToCancel)) {
log.warn("[cancelSelectedSubFlows] 未找到要取消的子单,changeRecordId:{}, cancelSubFlowIds:{}",
changeRecordId, cancelSubFlowIds);
return 0;
}
log.info("[cancelSelectedSubFlows] 开始取消指定子单,changeRecordId:{}, 待取消数量:{}, 总数量:{}, 取消原因:{}",
changeRecordId, subFlowsToCancel.size(), allSubFlows.size(), cancelReason);
int cancelledCount = 0;
int resetCount = 0;
int skippedCount = 0;
for (ChangeSubFlowRecord subFlowRecord : subFlowsToCancel) {
Integer currentStatus = subFlowRecord.getStatus();
// 直接遍历要驳回的子单并重置(需要流转工单流程)
for (ChangeSubFlowRecord subFlowRecord : subFlowsToReject) {
String subFlowId = subFlowRecord.getSubFlowId();
String oldNodeId = subFlowRecord.getSubFlowNode();
// 跳过已完成或已取消的子单
if (ChangeSubFlowStatusEnum.FINISHED.getStatus().equals(currentStatus)
|| ChangeSubFlowStatusEnum.CANCELLED.getStatus().equals(currentStatus)) {
log.info("[cancelAllSubFlows] 子单已完成或已取消,跳过, subFlowId:{}, status:{}", subFlowId, currentStatus);
try {
Integer currentStatus = subFlowRecord.getStatus();
// 跳过已取消的子单
if (ChangeSubFlowStatusEnum.CANCELLED.getStatus().equals(currentStatus)) {
log.info("[resetRejectedSubFlows] 子单已取消,跳过, subFlowId:{}, status:{}", subFlowId, currentStatus);
skippedCount++;
continue;
}
try {
// 查询子单流程详情
FlowDataDTO subFlowDataDTO = flowService.flowDetail(subFlowId);
if (subFlowDataDTO == null) {
log.warn("[cancelAllSubFlows] 子单流程查询失败,跳过, subFlowId:{}", subFlowId);
log.warn("[resetRejectedSubFlows] 子单流程查询失败,跳过, subFlowId:{}", subFlowId);
skippedCount++;
continue;
}
// 构建提交内容
Map<String, Object> cancelContent = new HashMap<>(CommonConstants.INIT_HASH_MAP_SIZE);
cancelContent.put("updateTime", System.currentTimeMillis());
cancelContent.put(CommonConstants.FLOW_OPERATION_KEY, FlowOperationTypeEnum.REFUSE.getValue());
Map<String, Object> subFlowContent = new HashMap<>(CommonConstants.INIT_HASH_MAP_SIZE);
subFlowContent.put("updateTime", System.currentTimeMillis());
subFlowContent.put(CommonConstants.FLOW_OPERATION_KEY, FlowOperationTypeEnum.PASS.getValue());
// 提交子单流程到结束节点(取消)
String cancelNodeId = flowService.submitFlow(subFlowId, subFlowDataDTO,
RequestLocalBean.getUid(), ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(),
JSON.toJSONString(cancelContent), false, FlowxOperationEnum.SUBMIT.getName(),
"主单审批不通过,自动取消", subFlowRecord.getCreateTime());
// 使用 paramMap 流转子单工单(type=2 回到初始节点)
Map<String, Object> paramMap = new HashMap<>();
paramMap.put("type", FlowTransitionType.TYPE_REJECTED);
// 更新子单记录
subFlowRecord.setSubFlowNode(cancelNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.CANCELLED.getStatus());
subFlowRecord.setCancelReason(cancelReason);
String startNodeId = flowService.submitFlowWithParamMap(subFlowId, subFlowDataDTO,
RequestLocalBean.getUid(), ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(),
JSON.toJSONString(subFlowContent), paramMap,
FlowxOperationEnum.SUBMIT.getName(), "主单审批不通过,驳回行动工单", subFlowRecord.getCreateTime());
// 重置子单记录到初始状态
subFlowRecord.setSubFlowNode(startNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.WAIT_CONFIRM_ACTION_PLAN.getStatus());
// 重置审批人为行动人自己
subFlowRecord.setApprover(JSON.toJSONString(Collections.singletonList(subFlowRecord.getChangeExecUserEmail())));
// 清空审批相关字段,保存驳回原因
subFlowRecord.setRejectReason(rejectReason);
subFlowRecord.setChangeResult(null);
subFlowRecord.setRemark(null);
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
cancelledCount++;
log.info("[cancelAllSubFlows] 子单已取消, subFlowId:{}, nodeId:{}", subFlowId, cancelNodeId);
resetCount++;
log.info("[resetRejectedSubFlows] 子单已驳回并重置到初始状态, subFlowId:{}, 原节点:{}, 新节点:{}, status:{}",
subFlowId, oldNodeId, startNodeId, ChangeSubFlowStatusEnum.WAIT_CONFIRM_ACTION_PLAN.getStatus());
} catch (Exception e) {
log.error("[cancelAllSubFlows] 取消子单失败, subFlowId:{}, error:{}", subFlowId, e.getMessage(), e);
// 继续取消下一个子单,不中断整个流程
log.error("[resetRejectedSubFlows] 重置子单失败, subFlowId:{}, error:{}", subFlowId, e.getMessage(), e);
// 继续重置下一个子单,不中断整个流程
}
}
log.info("[cancelSelectedSubFlows] 子单取消完成,changeRecordId:{}, 待取消数:{}, 已取消:{}, 跳过:{}",
changeRecordId, subFlowsToCancel.size(), cancelledCount, skippedCount);
log.info("[resetRejectedSubFlows] 子单重置完成,待重置数:{}, 已重置:{}, 跳过:{}",
subFlowsToReject.size(), resetCount, skippedCount);
return cancelledCount;
return resetCount;
}
}
......@@ -132,7 +132,7 @@ public class ChangeFlowSubmitReq {
private String rejectReason;
/**
* 要取消的子单ID列表(审批不通过时,选择要取消的行动工单
* 要驳回的子单ID列表(审批不通过时,选择要驳回的行动工单,必填
*/
private List<String> cancelSubFlowIds;
private List<String> rejectSubFlowIds;
}
\ No newline at end of file
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment