From ae69a7c328248d0013c5f1935afa871d3c632f4e Mon Sep 17 00:00:00 2001 From: "han\\hanst" Date: Mon, 30 Mar 2026 15:50:31 +0800 Subject: [PATCH] =?UTF-8?q?=E5=8F=91=E9=80=81=E5=8F=96=E6=B6=88=E9=80=9A?= =?UTF-8?q?=E7=9F=A5=E9=82=AE=E4=BB=B6=EF=BC=88=E8=A6=86=E7=9B=96=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=E4=BA=BA=E3=80=81=E8=B4=9F=E8=B4=A3=E4=BA=BA=E3=80=81?= =?UTF-8?q?=E7=BB=8F=E7=90=86=E3=80=81=E8=AE=A1=E5=88=92=E5=91=98=E5=8F=8A?= =?UTF-8?q?High=20Risk=E4=B8=89=E6=96=B9=E7=A1=AE=E8=AE=A4=E4=BA=BA?= =?UTF-8?q?=E5=91=98=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../service/impl/ErfExpApplyServiceImpl.java | 305 +++++++++++++++++- 1 file changed, 304 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfExpApplyServiceImpl.java b/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfExpApplyServiceImpl.java index 1c277673..d9312983 100644 --- a/src/main/java/com/xujie/sys/modules/erf/service/impl/ErfExpApplyServiceImpl.java +++ b/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 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> recipientRoleMap = collectCancelNotificationRecipients(entity); + if (operatorUserId != null && operatorUserId > 0) { + addRecipientRole(recipientRoleMap, operatorUserId, "取消操作人"); + } + if (recipientRoleMap.isEmpty()) { + log.warn("未收集到取消通知收件人,applyNo={}", entity.getApplyNo()); + return; + } + + LinkedHashSet emailSet = new LinkedHashSet<>(); + LinkedHashSet roleSummarySet = new LinkedHashSet<>(); + List recipientSummary = new ArrayList<>(); + + for (Map.Entry> 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> collectCancelNotificationRecipients(ErfExpApply entity) { + Map> 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> recipientRoleMap) { + QueryWrapper 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> 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> recipientRoleMap) { + QueryWrapper nodeQuery = new QueryWrapper<>(); + nodeQuery.eq("apply_no", applyNo).isNotNull("assignee_user_id"); + List 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> recipientRoleMap) { + QueryWrapper triQuery = new QueryWrapper<>(); + triQuery.eq("apply_no", applyNo); + List 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 roles = sysUserDao.getUserRolesByRoleNames( + userId, Arrays.asList("R&D经理", "MFG经理")); + if (roles == null || roles.isEmpty()) { + return "技术经理"; + } + LinkedHashSet 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> 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 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("") + .append("

工程实验申请取消通知

") + .append("

您好!以下试验单已被取消,相关审批流程已终止,请知悉。

") + .append("

取消人:") + .append(StringUtils.isNotBlank(operatorName) ? operatorName : "系统") + .append("

") + .append("

取消时间:") + .append(cancelTime) + .append("

") + .append("

通知范围:") + .append(roleSummary) + .append("

") + .append("
") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("") + .append("
试验单号事业部试验名称试验类型试验负责人需求日期状态
").append(entity.getApplyNo() != null ? entity.getApplyNo() : "").append("").append(entity.getBuNo() != null ? entity.getBuNo() : "").append("").append(entity.getTitle() != null ? entity.getTitle() : "").append("").append(entity.getExperimentType() != null ? entity.getExperimentType() : "").append("").append(entity.getProjectLeader() != null ? entity.getProjectLeader() : "").append("").append(expectedFinishDateStr).append("已取消
"); + + if ("High Risk".equalsIgnoreCase(entity.getExperimentType())) { + body.append("

说明:该试验单为 High Risk,三方确认相关人员已纳入本次通知范围。

"); + } + + body.append("

发送时间:") + .append(cancelTime) + .append("

") + .append(""); + return body.toString(); + } + @Override public String copyExpApply(String sourceApplyNo,String applyNo, Boolean copyTriConfirm, Boolean copyAttachment,Boolean copyRawMaterialList, Long currentUserId) { log.info("=== 开始复制试验单 === 源试验单号: {}, 复制三方确认: {}, 复制附件: {}",