Browse Source

发送取消通知邮件(覆盖申请人、负责人、经理、计划员及High Risk三方确认人员)

master
han\hanst 2 weeks ago
parent
commit
ae69a7c328
  1. 305
      src/main/java/com/xujie/sys/modules/erf/service/impl/ErfExpApplyServiceImpl.java

305
src/main/java/com/xujie/sys/modules/erf/service/impl/ErfExpApplyServiceImpl.java

@ -3,6 +3,8 @@ package com.xujie.sys.modules.erf.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.xujie.sys.common.exception.XJException;
@ -58,6 +60,7 @@ import static com.xujie.sys.modules.sys.controller.AbstractController.getUser;
public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfExpApply> implements ErfExpApplyService {
private static final String APPROVAL_PAGE_URL = "http://172.26.68.20:9001/#/erf-expApplyApproval";
private static final ObjectMapper JSON_MAPPER = new ObjectMapper();
@Autowired
private ErfExpApplyMapper erfExpApplyMapper;
@ -88,6 +91,9 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx
@Autowired
private QcMapper qcMapper;
@Autowired
private ErfExpTriConfirmMapper erfExpTriConfirmMapper;
@Override
public PageUtils searchExpApplyList(ErfExpApplyData data) {
PageHelper.startPage(data.getPage(), data.getLimit());
@ -1481,13 +1487,310 @@ public class ErfExpApplyServiceImpl extends ServiceImpl<ErfExpApplyMapper, ErfEx
}
// 6. 记录操作日志
String operatorName = getUser() != null ? getUser().getUsername() : "系统";
String operatorName = "系统";
if (getUser() != null) {
operatorName = StringUtils.isNotBlank(getUser().getUserDisplay())
? getUser().getUserDisplay()
: getUser().getUsername();
}
saveApprovalLog(applyNo, "取消", "取消试验单",
"用户主动取消试验单,所有待审批流程已终止", userId, operatorName);
// 7. 发送取消通知邮件覆盖申请人负责人经理计划员及High Risk三方确认人员
sendCancelNotification(entity, userId, operatorName);
log.info("=== 取消试验单完成 === 试验单号: {}", applyNo);
}
/**
* 取消后发送通知邮件
* 通知范围实验申请人试验负责人技术/生产/质量经理计划员PJM负责人
* High Risk 三方确认人员流程历史相关审批人
*/
private void sendCancelNotification(ErfExpApply entity, Long operatorUserId, String operatorName) {
try {
MailSendAddressData mailSendData = qcMapper.getSendMailFromAddress();
if (mailSendData == null) {
log.error("邮件发送配置未设置,无法发送试验单取消通知,applyNo={}", entity.getApplyNo());
return;
}
Map<Long, Set<String>> recipientRoleMap = collectCancelNotificationRecipients(entity);
if (operatorUserId != null && operatorUserId > 0) {
addRecipientRole(recipientRoleMap, operatorUserId, "取消操作人");
}
if (recipientRoleMap.isEmpty()) {
log.warn("未收集到取消通知收件人,applyNo={}", entity.getApplyNo());
return;
}
LinkedHashSet<String> emailSet = new LinkedHashSet<>();
LinkedHashSet<String> roleSummarySet = new LinkedHashSet<>();
List<String> recipientSummary = new ArrayList<>();
for (Map.Entry<Long, Set<String>> entry : recipientRoleMap.entrySet()) {
Long userId = entry.getKey();
UserEmailInfoDto userInfo = sysUserDao.getUserEmailInfoById(userId);
if (userInfo == null) {
log.warn("取消通知收件人不存在,userId={}, applyNo={}", userId, entity.getApplyNo());
continue;
}
String email = userInfo.getEmail() != null ? userInfo.getEmail().trim() : "";
if (email.isEmpty()) {
log.warn("取消通知收件人未配置邮箱,userId={}, username={}, applyNo={}",
userId, userInfo.getUsername(), entity.getApplyNo());
continue;
}
emailSet.add(email);
roleSummarySet.addAll(entry.getValue());
recipientSummary.add(String.format("%s(%s)",
userInfo.getUsername(), String.join("、", entry.getValue())));
}
if (emailSet.isEmpty()) {
log.warn("已收集收件人但无有效邮箱,取消通知未发送,applyNo={}", entity.getApplyNo());
return;
}
String subject = String.format("【工程实验申请取消通知】%s - %s",
entity.getApplyNo(), StringUtils.isNotBlank(entity.getTitle()) ? entity.getTitle() : "无标题");
String body = buildCancelNotificationBody(entity, operatorName, roleSummarySet);
MailUtil.sendMail(subject, body, emailSet.toArray(new String[0]), mailSendData);
SendMailRecord mailRecord = new SendMailRecord();
mailRecord.setType("工程实验申请取消通知");
mailRecord.setDocumentNo(entity.getApplyNo());
mailRecord.setRecipient(String.join(";", emailSet));
mailRecord.setSendDate(new Date());
qcMapper.saveSendMailRecord(mailRecord);
log.info("取消通知邮件发送完成,applyNo={}, 收件人数量={}, 收件人={}",
entity.getApplyNo(), emailSet.size(), recipientSummary);
} catch (Exception e) {
log.error("发送试验单取消通知失败,applyNo={}, error={}", entity.getApplyNo(), e.getMessage(), e);
}
}
private Map<Long, Set<String>> collectCancelNotificationRecipients(ErfExpApply entity) {
Map<Long, Set<String>> recipientRoleMap = new LinkedHashMap<>();
// 1. 申请人
addRecipientRole(recipientRoleMap, entity.getCreatorUserId(), "实验申请人");
// 2. 试验负责人
UserEmailInfoDto projectLeader = resolveProjectLeaderEmailInfo(entity);
if (projectLeader != null) {
addRecipientRole(recipientRoleMap, projectLeader.getUserId(), "试验负责人");
}
// 3. PJM负责人
UserEmailInfoDto pjmLeader = resolvePjmLeaderEmailInfo(entity);
if (pjmLeader != null) {
addRecipientRole(recipientRoleMap, pjmLeader.getUserId(), "PJM负责人");
}
// 4. 下达时配置的技术/生产/质量经理与计划员
collectApproversFromFlowRemark(entity.getApplyNo(), recipientRoleMap);
// 5. 流程节点中出现过的相关审批人兜底覆盖
collectFlowNodeAssignees(entity.getApplyNo(), recipientRoleMap);
// 6. High Risk 额外包含三方确认人员
if ("High Risk".equalsIgnoreCase(entity.getExperimentType())) {
collectTriConfirmApprovers(entity.getApplyNo(), recipientRoleMap);
}
return recipientRoleMap;
}
private void collectApproversFromFlowRemark(String applyNo, Map<Long, Set<String>> recipientRoleMap) {
QueryWrapper<ErfFlowInstance> flowQuery = new QueryWrapper<>();
flowQuery.eq("apply_no", applyNo);
ErfFlowInstance flowInstance = erfFlowInstanceMapper.selectOne(flowQuery);
if (flowInstance == null || StringUtils.isBlank(flowInstance.getRemark())) {
return;
}
try {
JsonNode root = JSON_MAPPER.readTree(flowInstance.getRemark());
long techManagerId = root.path("techManagerId").asLong(0);
if (techManagerId > 0) {
addRecipientRole(recipientRoleMap, techManagerId, resolveTechManagerRoleLabel(techManagerId));
}
long prodManagerId = root.path("prodManagerId").asLong(0);
if (prodManagerId > 0) {
addRecipientRole(recipientRoleMap, prodManagerId, "生产经理");
}
addRecipientsFromJsonArray(root.path("prodManagerIds"), "生产经理", recipientRoleMap);
long qualityManagerId = root.path("qualityManagerId").asLong(0);
if (qualityManagerId > 0) {
addRecipientRole(recipientRoleMap, qualityManagerId, "质量经理");
}
addRecipientsFromJsonArray(root.path("qualityManagerIds"), "质量经理", recipientRoleMap);
long plannerId = root.path("plannerId").asLong(0);
if (plannerId > 0) {
addRecipientRole(recipientRoleMap, plannerId, "计划员");
}
addRecipientsFromJsonArray(root.path("plannerIds"), "计划员", recipientRoleMap);
} catch (Exception e) {
log.warn("解析流程审批人信息失败,applyNo={}, error={}", applyNo, e.getMessage());
}
}
private void addRecipientsFromJsonArray(JsonNode arrayNode, String roleName,
Map<Long, Set<String>> recipientRoleMap) {
if (arrayNode == null || !arrayNode.isArray()) {
return;
}
for (JsonNode node : arrayNode) {
long userId = node.asLong(0);
if (userId > 0) {
addRecipientRole(recipientRoleMap, userId, roleName);
}
}
}
private void collectFlowNodeAssignees(String applyNo, Map<Long, Set<String>> recipientRoleMap) {
QueryWrapper<ErfFlowNodeInstance> nodeQuery = new QueryWrapper<>();
nodeQuery.eq("apply_no", applyNo).isNotNull("assignee_user_id");
List<ErfFlowNodeInstance> nodeInstances = erfFlowNodeInstanceMapper.selectList(nodeQuery);
if (nodeInstances == null || nodeInstances.isEmpty()) {
return;
}
for (ErfFlowNodeInstance node : nodeInstances) {
addRecipientRole(recipientRoleMap, node.getAssigneeUserId(),
mapNodeCodeToCancelNotifyRole(node.getNodeCode(), node.getAssigneeUserId()));
}
}
private String mapNodeCodeToCancelNotifyRole(String nodeCode, Long assigneeUserId) {
if (StringUtils.isBlank(nodeCode)) {
return "流程相关人员";
}
if (nodeCode.contains("技术经理")) {
return resolveTechManagerRoleLabel(assigneeUserId);
}
if (nodeCode.contains("生产经理")) {
return "生产经理";
}
if (nodeCode.contains("质量经理")) {
return "质量经理";
}
if (nodeCode.contains("计划员")) {
return "计划员";
}
if (nodeCode.contains("三方确认")) {
return "三方确认人员";
}
return "流程相关人员";
}
private void collectTriConfirmApprovers(String applyNo, Map<Long, Set<String>> recipientRoleMap) {
QueryWrapper<ErfExpTriConfirm> triQuery = new QueryWrapper<>();
triQuery.eq("apply_no", applyNo);
List<ErfExpTriConfirm> triConfirmList = erfExpTriConfirmMapper.selectList(triQuery);
if (triConfirmList == null || triConfirmList.isEmpty()) {
return;
}
for (ErfExpTriConfirm tri : triConfirmList) {
addRecipientRole(recipientRoleMap, tri.getProdApproverUserId(), "生产负责人");
addRecipientRole(recipientRoleMap, tri.getQaApproverUserId(), "质量负责人");
addRecipientRole(recipientRoleMap, tri.getTechApproverUserId(), "技术负责人");
}
}
/**
* 解析技术经理的真实角色名称R&D经理 / MFG经理
* 若无法识别则回退为技术经理
*/
private String resolveTechManagerRoleLabel(Long userId) {
if (userId == null || userId <= 0) {
return "技术经理";
}
try {
List<UserRoleDto> roles = sysUserDao.getUserRolesByRoleNames(
userId, Arrays.asList("R&D经理", "MFG经理"));
if (roles == null || roles.isEmpty()) {
return "技术经理";
}
LinkedHashSet<String> roleNames = roles.stream()
.map(UserRoleDto::getRoleName)
.filter(StringUtils::isNotBlank)
.collect(Collectors.toCollection(LinkedHashSet::new));
if (roleNames.isEmpty()) {
return "技术经理";
}
return String.join("、", roleNames);
} catch (Exception e) {
log.warn("解析技术经理角色失败,userId={}, error={}", userId, e.getMessage());
return "技术经理";
}
}
private void addRecipientRole(Map<Long, Set<String>> recipientRoleMap, Long userId, String roleName) {
if (userId == null || userId <= 0 || StringUtils.isBlank(roleName)) {
return;
}
recipientRoleMap
.computeIfAbsent(userId, key -> new LinkedHashSet<>())
.add(roleName);
}
private String buildCancelNotificationBody(ErfExpApply entity, String operatorName, Set<String> roleSummarySet) {
String cancelTime = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
String expectedFinishDateStr = entity.getExpectedFinishDate() != null
? DateTimeFormatter.ofPattern("yyyy-MM-dd")
.format(entity.getExpectedFinishDate().toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime())
: "";
String roleSummary = roleSummarySet == null || roleSummarySet.isEmpty()
? "流程相关人员"
: String.join("、", roleSummarySet);
StringBuilder body = new StringBuilder();
body.append("<html><body>")
.append("<h3>工程实验申请取消通知</h3>")
.append("<p>您好!以下试验单已被取消,相关审批流程已终止,请知悉。</p>")
.append("<p><b>取消人:</b>")
.append(StringUtils.isNotBlank(operatorName) ? operatorName : "系统")
.append("</p>")
.append("<p><b>取消时间:</b>")
.append(cancelTime)
.append("</p>")
.append("<p><b>通知范围:</b>")
.append(roleSummary)
.append("</p>")
.append("<hr/>")
.append("<table border='1' cellpadding='6' cellspacing='0' style='border-collapse:collapse; width:100%; font-size:13px;'>")
.append("<thead style='background-color:#909399; color:white;'><tr>")
.append("<th>试验单号</th><th>事业部</th><th>试验名称</th><th>试验类型</th><th>试验负责人</th><th>需求日期</th><th>状态</th>")
.append("</tr></thead><tbody><tr>")
.append("<td>").append(entity.getApplyNo() != null ? entity.getApplyNo() : "").append("</td>")
.append("<td>").append(entity.getBuNo() != null ? entity.getBuNo() : "").append("</td>")
.append("<td>").append(entity.getTitle() != null ? entity.getTitle() : "").append("</td>")
.append("<td>").append(entity.getExperimentType() != null ? entity.getExperimentType() : "").append("</td>")
.append("<td>").append(entity.getProjectLeader() != null ? entity.getProjectLeader() : "").append("</td>")
.append("<td>").append(expectedFinishDateStr).append("</td>")
.append("<td>已取消</td>")
.append("</tr></tbody></table>");
if ("High Risk".equalsIgnoreCase(entity.getExperimentType())) {
body.append("<p><b>说明:</b>该试验单为 High Risk,三方确认相关人员已纳入本次通知范围。</p>");
}
body.append("<p style='color:#888;'>发送时间:")
.append(cancelTime)
.append("</p>")
.append("</body></html>");
return body.toString();
}
@Override
public String copyExpApply(String sourceApplyNo,String applyNo, Boolean copyTriConfirm, Boolean copyAttachment,Boolean copyRawMaterialList, Long currentUserId) {
log.info("=== 开始复制试验单 === 源试验单号: {}, 复制三方确认: {}, 复制附件: {}",

Loading…
Cancel
Save