Commit c8fc33d3 by 王志超

feat: 统一处理主单审批节点的流转逻辑

parent a240240c
......@@ -711,55 +711,44 @@ public class ChangeFlowBiz {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
"该节点不允许主动提交,需等待所有子单审批通过后自动流转");
case NEW_CHANGE_FLOW_OWNER_APPROVE:
case NEW_CHANGE_FLOW_ADMIN_APPROVE:
case NEW_CHANGE_FLOW_QUALITY_APPROVE:
// 部门负责人、变更管理员、质量部负责人审批节点
Boolean mainFlowApproved = changeFlowSubmitReq.getApproved();
if (mainFlowApproved == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "审批结果不能为空");
}
// 部门负责人审批节点:设置下一个审批人为变更管理员
String changeAdmin = appConfig.getChangeAdmin();
Map<String, Object> ownerApproveParamMap = new HashMap<>();
ownerApproveParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
return handleMainFlowApprovalSubmit(changeFlowSubmitReq, flowId, flowDataDTO, uid, changeRecord, node, content,
ownerApproveParamMap, changeAdmin, ChangeStatusEnum.WAIT_CHANGE_ADMIN_APPROVE);
// 使用paramMap控制流转方向
Map<String, Object> mainFlowParamMap = new HashMap<>();
String mainFlowNodeId;
case NEW_CHANGE_FLOW_ADMIN_APPROVE:
// 变更管理员审批节点:根据变更等级判断下一步
// 重要变更(changeLevel=1):需要质量部负责人审批,type=1
// 一般变更(changeLevel=2):跳过质量部审批,直接到执行变更方案,type=4
Integer changeLevel = changeRecord.getChangeLevel();
Map<String, Object> adminApproveParamMap = new HashMap<>();
String nextApproverForAdmin;
ChangeStatusEnum nextStatusForAdmin;
if (mainFlowApproved) {
// 审批通过:流转到下一节点
log.info("[{}] 审批通过,flowId:{}", node.getName(), flowId);
mainFlowParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
mainFlowNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), mainFlowParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批通过", changeRecord.getCreateTime());
// 更新主工单记录
changeRecord.setFlowNode(mainFlowNodeId);
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
changeFlowService.updateRecord(changeRecord);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:{}, 审批结果:通过",
flowId, node.getName(), mainFlowNodeId);
if (ChangeLevelEnum.IMPORTANT.getType().equals(changeLevel)) {
// 重要变更:走质量部负责人审批
adminApproveParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
nextApproverForAdmin = appConfig.getChangeQualityLeader();
nextStatusForAdmin = ChangeStatusEnum.WAIT_QUALITY_LEADER_APPROVE;
log.info("[NEW_CHANGE_FLOW_ADMIN_APPROVE] 重要变更,需要质量部负责人审批");
} else {
// 审批不通过:回退到确认变更方案节点
// todo: 实现审批不通过的逻辑(拒绝原因、状态更新、邮件通知等)
log.warn("[{}] 审批不通过,flowId:{}", node.getName(), flowId);
mainFlowParamMap.put("type", FlowTransitionType.TYPE_BACK_TO_CONFIRM_PLAN);
mainFlowNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), mainFlowParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批不通过", changeRecord.getCreateTime());
// 更新主工单记录(回到确认变更方案节点)
changeRecord.setFlowNode(mainFlowNodeId);
changeRecord.setState(ChangeStatusEnum.WAIT_CONFIRM_CHANGE_PLAN.getStatus());
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
changeFlowService.updateRecord(changeRecord);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:确认变更方案, 审批结果:不通过",
flowId, node.getName());
// 一般变更:跳过质量部负责人审批
adminApproveParamMap.put("type", FlowTransitionType.TYPE_SKIP_QUALITY_APPROVE);
nextApproverForAdmin = null;
nextStatusForAdmin = ChangeStatusEnum.WAIT_EXEC_CHANGE_PLAN;
log.info("[NEW_CHANGE_FLOW_ADMIN_APPROVE] 一般变更,跳过质量部负责人审批");
}
return handleMainFlowApprovalSubmit(changeFlowSubmitReq, flowId, flowDataDTO, uid, changeRecord, node, content,
adminApproveParamMap, nextApproverForAdmin, nextStatusForAdmin);
return mainFlowNodeId;
case NEW_CHANGE_FLOW_QUALITY_APPROVE:
// 质量部负责人审批节点:审批通过后流转到执行变更方案
Map<String, Object> qualityApproveParamMap = new HashMap<>();
qualityApproveParamMap.put("type", FlowTransitionType.TYPE_APPROVED);
return handleMainFlowApprovalSubmit(changeFlowSubmitReq, flowId, flowDataDTO, uid, changeRecord, node, content,
qualityApproveParamMap, null, ChangeStatusEnum.WAIT_EXEC_CHANGE_PLAN);
case NEW_CHANGE_FLOW_EXE:
// 执行变更方案节点:等待所有子单完成后自动流转,不允许主动提交
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
......@@ -1613,6 +1602,97 @@ public class ChangeFlowBiz {
*
* @param changeRecordId 主工单ID
*/
/**
* 统一处理主单审批节点的流转逻辑(包含审批通过和审批不通过)
*
* @param changeFlowSubmitReq 提交请求
* @param flowId 工单ID
* @param flowDataDTO 工单详情
* @param uid 当前用户
* @param changeRecord 主工单记录
* @param node 当前节点
* @param content 提交内容
* @param approvedParamMap 审批通过时的 paramMap
* @param nextApprover 下一个审批人邮箱(可选,为空则不设置)
* @param nextStatus 下一个状态(可选,为空则不设置)
* @return 下一节点ID
*/
private String handleMainFlowApprovalSubmit(ChangeFlowSubmitReq changeFlowSubmitReq, Long flowId, FlowDataDTO flowDataDTO,
String uid, ChangeRecord changeRecord, ChangeFlowEnum node,
Map<String, Object> content, Map<String, Object> approvedParamMap,
String nextApprover, ChangeStatusEnum nextStatus) {
Boolean approved = changeFlowSubmitReq.getApproved();
if (approved == null) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "审批结果不能为空");
}
String nextNodeId;
if (approved) {
// 审批通过:流转到下一节点
log.info("[{}] 审批通过,flowId:{}", node.getName(), flowId);
nextNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), approvedParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批通过", changeRecord.getCreateTime());
// 更新主工单记录
changeRecord.setFlowNode(nextNodeId);
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
// 设置下一个审批人(如果提供)
if (StringUtils.isNotBlank(nextApprover)) {
changeRecord.setApprover(JSON.toJSONString(Collections.singletonList(nextApprover)));
log.info("[{}] 下一个审批人:{}", node.getName(), nextApprover);
}
// 设置下一个状态(如果提供)
if (nextStatus != null) {
changeRecord.setState(nextStatus.getStatus());
}
changeFlowService.updateRecord(changeRecord);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:{}, 审批结果:通过",
flowId, node.getName(), nextNodeId);
} else {
// 审批不通过:回退到确认变更方案节点,并取消所有子单
log.warn("[{}] 审批不通过,flowId:{}", node.getName(), flowId);
Map<String, Object> rejectParamMap = new HashMap<>();
rejectParamMap.put("type", FlowTransitionType.TYPE_BACK_TO_CONFIRM_PLAN);
nextNodeId = flowService.submitFlowWithParamMap(flowId.toString(), flowDataDTO, uid,
ChangeFlowEnum.NEW_CHANGE_FLOW.getTopoId(), JSON.toJSONString(content), rejectParamMap,
FlowxOperationEnum.SUBMIT.getName(), "审批不通过", changeRecord.getCreateTime());
// 更新主工单记录(回到确认变更方案节点)
changeRecord.setFlowNode(nextNodeId);
changeRecord.setState(ChangeStatusEnum.WAIT_CONFIRM_CHANGE_PLAN.getStatus());
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);
}
// 发送审批不通过邮件通知
sendMainFlowRejectEmail(changeRecord, node.getName(), rejectReason, cancelledCount);
log.info("[checkUpdateAndSubmit] 主工单流转完成,flowId:{}, 审批节点:{}, 新节点:确认变更方案, 审批结果:不通过,已取消子单数:{}",
flowId, node.getName(), cancelledCount);
}
return nextNodeId;
}
void submitMainFlowAfterAllSubFlowsApproved(Long changeRecordId) {
// 查询主工单
ChangeRecord changeRecord = changeRecordMapper.selectByPrimaryKey(changeRecordId);
......@@ -1655,10 +1735,23 @@ public class ChangeFlowBiz {
changeRecord.setFlowNode(ownerApproveNodeId);
changeRecord.setState(ChangeStatusEnum.WAIT_DEPT_LEADER_APPROVE.getStatus());
changeRecord.setUpdateTime(DateUtils.getCurrentTime());
// 获取变更负责人的上级领导并设置为审批人
String changeCommander = changeRecord.getChangeCommander();
if (StringUtils.isBlank(changeCommander)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST, "变更负责人邮箱不能为空");
}
String leaderEmail = departmentLeaderBiz.getDepartmentLeader(changeCommander);
if (StringUtils.isBlank(leaderEmail)) {
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
String.format("变更负责人[%s]的部门上级领导不存在", changeCommander));
}
// 设置审批人为上级领导(JSON 数组格式)
changeRecord.setApprover(JSON.toJSONString(Collections.singletonList(leaderEmail)));
changeFlowService.updateRecord(changeRecord);
log.info("[submitMainFlowAfterAllSubFlowsApproved] 主工单流转成功,flowId:{}, nextNodeId:{}",
flowId, ownerApproveNodeId);
log.info("[submitMainFlowAfterAllSubFlowsApproved] 主工单流转成功,flowId:{}, nextNodeId:{}, approver:{}",
flowId, ownerApproveNodeId, leaderEmail);
} catch (Exception e) {
log.error("[submitMainFlowAfterAllSubFlowsApproved] 主工单流转失败,changeRecordId:{}", changeRecordId, e);
throw ExceptionFactory.createBiz(ResponseCode.BAD_REQUEST,
......@@ -1686,5 +1779,172 @@ public class ChangeFlowBiz {
|| ChangeSubFlowStatusEnum.CANCELLED.getStatus().equals(subFlow.getStatus())
);
}
/**
* 发送主单审批不通过邮件通知
*
* @param changeRecord 主工单记录
* @param rejectNodeName 审批不通过的节点名称
* @param rejectReason 拒绝原因
* @param cancelledCount 取消的子单数量
*/
private void sendMainFlowRejectEmail(ChangeRecord changeRecord, String rejectNodeName, String rejectReason, int cancelledCount) {
try {
// 构建邮件参数
Map<String, Object> emailParam = new HashMap<>();
emailParam.put("changeId", changeRecord.getFlowId());
emailParam.put("changeSubject", ChangeSubjectEnum.getChangeSubjectEnum(changeRecord.getChangeSubject()).getDesc());
// 构建变更类型
ChangeType parentChangeType = changeTypeService.getChangeTypeById(changeRecord.getParentChangeClassId());
ChangeType sonChangeType = changeTypeService.getChangeTypeById(changeRecord.getSonChangeClassId());
if (parentChangeType != null && sonChangeType != null) {
emailParam.put("changeType", parentChangeType.getTypeName() + ">" + sonChangeType.getTypeName());
}
emailParam.put("changeContent", changeRecord.getChangeContent());
emailParam.put("rejectNodeName", rejectNodeName);
emailParam.put("rejectReason", rejectReason);
emailParam.put("cancelledSubFlowCount", cancelledCount);
emailParam.put("flowUrl", changeRecord.getFlowId());
String subjectParam = changeRecord.getFlowId().toString();
// 收件人:变更负责人、变更发起人
List<String> receiver = new ArrayList<>();
String changeCommander = changeRecord.getChangeCommander();
receiver.add(changeCommander);
String creator = changeRecord.getCreator();
if (!creator.equals(changeCommander)) {
receiver.add(creator);
}
// 抄送人:变更负责人上一级主管、变更管理QM、所有行动人
List<String> ccList = new ArrayList<>();
ccList.addAll(departmentLeaderBiz.getDepartmentLeaders(Collections.singletonList(changeCommander)));
ccList.add(appConfig.getChangeManageQM());
// 添加所有行动人到抄送列表
List<ChangeFlowExecVO> execRecord = changeFlowExecService.getChangeFlowExecRecord(changeRecord.getId());
if (CollectionUtils.isNotEmpty(execRecord)) {
List<String> execUserEmails = execRecord.stream()
.map(ChangeFlowExecVO::getChangeExecUserEmail)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toList());
ccList.addAll(execUserEmails);
}
// 发送邮件(使用"发起变更"模板,因为需要重新提交)
qcSendEmail(receiver, ccList, subjectParam, EmailTemplateEnum.YX_QC_CHANGE_RELEASE_FLOW, emailParam);
// 如果是供应商相关,发送供应商邮件
if (CreateSourceEnum.TONG_ZHOU.getType().equals(changeRecord.getCreateSource())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getCreateSupplier(), subjectParam,
EmailTemplateEnum.YX_QC_CHANGE_RELEASE_FLOW, emailParam);
} else if (ChangeSubjectEnum.SUPPLIER.getType().equals(changeRecord.getChangeSubject())) {
sendSupplierEmail.sendSupplierEmail(changeRecord.getChangeSupplier(), subjectParam,
EmailTemplateEnum.YX_QC_CHANGE_RELEASE_FLOW, emailParam);
}
log.info("[sendMainFlowRejectEmail] 审批不通过邮件已发送,flowId:{}, rejectNode:{}",
changeRecord.getFlowId(), rejectNodeName);
} catch (Exception e) {
log.error("[sendMainFlowRejectEmail] 发送审批不通过邮件失败,flowId:{}, error:{}",
changeRecord.getFlowId(), e.getMessage(), e);
}
}
/**
* 取消主工单下指定的子单(主单审批不通过时调用)
*
* @param changeRecordId 主工单ID
* @param cancelSubFlowIds 要取消的子单ID列表
* @param cancelReason 取消原因
* @return 实际取消的子单数量
*/
private int cancelSelectedSubFlows(Long changeRecordId, List<String> cancelSubFlowIds, String cancelReason) {
if (CollectionUtils.isEmpty(cancelSubFlowIds)) {
log.info("[cancelSelectedSubFlows] 未指定要取消的子单,changeRecordId:{}", changeRecordId);
return 0;
}
// 查询该主工单下所有子单
List<ChangeSubFlowRecord> allSubFlows = changeSubFlowRecordMapper.selectByChangeRecordId(changeRecordId);
if (CollectionUtils.isEmpty(allSubFlows)) {
log.info("[cancelSelectedSubFlows] 主工单没有子单,changeRecordId:{}", changeRecordId);
return 0;
}
// 过滤出要取消的子单
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 skippedCount = 0;
for (ChangeSubFlowRecord subFlowRecord : subFlowsToCancel) {
Integer currentStatus = subFlowRecord.getStatus();
String subFlowId = subFlowRecord.getSubFlowId();
// 跳过已完成或已取消的子单
if (ChangeSubFlowStatusEnum.FINISHED.getStatus().equals(currentStatus)
|| ChangeSubFlowStatusEnum.CANCELLED.getStatus().equals(currentStatus)) {
log.info("[cancelAllSubFlows] 子单已完成或已取消,跳过, subFlowId:{}, status:{}", subFlowId, currentStatus);
skippedCount++;
continue;
}
try {
// 查询子单流程详情
FlowDataDTO subFlowDataDTO = flowService.flowDetail(subFlowId);
if (subFlowDataDTO == null) {
log.warn("[cancelAllSubFlows] 子单流程查询失败,跳过, 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());
// 提交子单流程到结束节点(取消)
String cancelNodeId = flowService.submitFlow(subFlowId, subFlowDataDTO,
RequestLocalBean.getUid(), ChangeFlowEnum.CHANGE_SUB_FLOW.getTopoId(),
JSON.toJSONString(cancelContent), false, FlowxOperationEnum.SUBMIT.getName(),
"主单审批不通过,自动取消", subFlowRecord.getCreateTime());
// 更新子单记录
subFlowRecord.setSubFlowNode(cancelNodeId);
subFlowRecord.setStatus(ChangeSubFlowStatusEnum.CANCELLED.getStatus());
subFlowRecord.setCancelReason(cancelReason);
subFlowRecord.setUpdateTime(DateUtils.getCurrentTime());
changeSubFlowRecordService.update(subFlowRecord);
cancelledCount++;
log.info("[cancelAllSubFlows] 子单已取消, subFlowId:{}, nodeId:{}", subFlowId, cancelNodeId);
} catch (Exception e) {
log.error("[cancelAllSubFlows] 取消子单失败, subFlowId:{}, error:{}", subFlowId, e.getMessage(), e);
// 继续取消下一个子单,不中断整个流程
}
}
log.info("[cancelSelectedSubFlows] 子单取消完成,changeRecordId:{}, 待取消数:{}, 已取消:{}, 跳过:{}",
changeRecordId, subFlowsToCancel.size(), cancelledCount, skippedCount);
return cancelledCount;
}
}
......@@ -95,6 +95,18 @@ public class AppConfig {
*/
@Value("${changeManageQM:cuiyixian@corp.netease.com}")
private String changeManageQM;
/**
* 变更管理员(用于主单审批流程)
*/
@Value("${changeAdmin:}")
private String changeAdmin;
/**
* 质量部负责人(用于主单审批流程)
*/
@Value("${changeQualityLeader:}")
private String changeQualityLeader;
/**
* 变更管理委员会
......
......@@ -24,6 +24,11 @@ public class FlowTransitionType {
*/
public static final Integer TYPE_BACK_TO_CONFIRM_PLAN = 3;
/**
* 类型4:跳过质量部负责人审批,直接到执行变更方案节点(一般变更专用)
*/
public static final Integer TYPE_SKIP_QUALITY_APPROVE = 4;
private FlowTransitionType() {
// 工具类,禁止实例化
}
......
......@@ -130,4 +130,9 @@ public class ChangeFlowSubmitReq {
* 拒绝原因(审批不通过时必填)
*/
private String rejectReason;
/**
* 要取消的子单ID列表(审批不通过时,选择要取消的行动工单)
*/
private List<String> cancelSubFlowIds;
}
\ 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