|
|
|
@ -1,14 +1,18 @@ |
|
|
|
package com.xujie.sys.modules.erf.service.impl; |
|
|
|
|
|
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; |
|
|
|
import com.fasterxml.jackson.databind.JsonNode; |
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper; |
|
|
|
import com.xujie.sys.common.utils.MailUtil; |
|
|
|
import com.xujie.sys.modules.erf.entity.ErfExpApply; |
|
|
|
import com.xujie.sys.modules.erf.entity.ErfExpTriConfirm; |
|
|
|
import com.xujie.sys.modules.erf.entity.ErfExpTriConfirmDetail; |
|
|
|
import com.xujie.sys.modules.erf.entity.ErfFlowInstance; |
|
|
|
import com.xujie.sys.modules.erf.entity.ErfFlowNodeInstance; |
|
|
|
import com.xujie.sys.modules.erf.mapper.ErfExpApplyMapper; |
|
|
|
import com.xujie.sys.modules.erf.mapper.ErfExpTriConfirmDetailMapper; |
|
|
|
import com.xujie.sys.modules.erf.mapper.ErfExpTriConfirmMapper; |
|
|
|
import com.xujie.sys.modules.erf.mapper.ErfFlowInstanceMapper; |
|
|
|
import com.xujie.sys.modules.erf.mapper.ErfFlowNodeInstanceMapper; |
|
|
|
import com.xujie.sys.modules.erf.service.ErfApprovalReminderService; |
|
|
|
import com.xujie.sys.modules.pms.data.MailSendAddressData; |
|
|
|
@ -39,6 +43,9 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
@Autowired |
|
|
|
private ErfFlowNodeInstanceMapper erfFlowNodeInstanceMapper; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private ErfFlowInstanceMapper erfFlowInstanceMapper; |
|
|
|
|
|
|
|
@Autowired |
|
|
|
private ErfExpApplyMapper erfExpApplyMapper; |
|
|
|
|
|
|
|
@ -54,6 +61,11 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
@Autowired |
|
|
|
private ErfExpTriConfirmMapper erfExpTriConfirmMapper; |
|
|
|
|
|
|
|
private static final ObjectMapper JSON_MAPPER = new ObjectMapper(); |
|
|
|
|
|
|
|
private static final String APPROVAL_PAGE_URL = "http://172.26.68.20:9001/#/erf-expApplyApproval"; |
|
|
|
private static final String PLANNER_PAGE_URL = "http://172.26.68.20:9001/#/erf-plannerSchedule"; |
|
|
|
|
|
|
|
@Value("${erf.reminder.manager.enabled:false}") |
|
|
|
private boolean managerReminderEnabled; |
|
|
|
|
|
|
|
@ -475,7 +487,7 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
emailBody.append("</tbody>"); |
|
|
|
emailBody.append("</table>"); |
|
|
|
emailBody.append("<br/>"); |
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='http://172.26.68.20:9001/#/erf-expApplyApproval'>前往查看</a>"); |
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='").append(APPROVAL_PAGE_URL).append("'>前往查看</a>"); |
|
|
|
emailBody.append("<p style='color: #888;'>发送时间:").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("</p>"); |
|
|
|
emailBody.append("</body></html>"); |
|
|
|
|
|
|
|
@ -548,7 +560,7 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
emailBody.append("</tbody>"); |
|
|
|
emailBody.append("</table>"); |
|
|
|
emailBody.append("<br/>"); |
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='http://172.26.68.20:9001/#/erf-plannerSchedule'>前往查看</a>"); |
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='").append(PLANNER_PAGE_URL).append("'>前往查看</a>"); |
|
|
|
emailBody.append("<p style='color: #888;'>发送时间:").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("</p>"); |
|
|
|
emailBody.append("</body></html>"); |
|
|
|
|
|
|
|
@ -639,7 +651,7 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
emailBody.append("</table>"); |
|
|
|
} |
|
|
|
|
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='http://172.26.68.20:9001/#/erf-triConfirm'>前往查看</a>"); |
|
|
|
emailBody.append("<a style='color:#0f84b0;' href='http://172.26.68.20:9001/#/erf-triConfirm'>前往三方确认页面</a>"); |
|
|
|
emailBody.append("<p style='color: #888;'>发送时间:").append(LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"))).append("</p>"); |
|
|
|
emailBody.append("</body></html>"); |
|
|
|
|
|
|
|
@ -708,6 +720,323 @@ public class ErfApprovalReminderServiceImpl implements ErfApprovalReminderServic |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
@Override |
|
|
|
public int sendManualUrgeEmail(String applyNo) { |
|
|
|
log.info("=== 开始手动催办 === 申请单: {}", applyNo); |
|
|
|
|
|
|
|
// 1. 查询申请单基本信息 |
|
|
|
ErfExpApply apply = erfExpApplyMapper.selectOne( |
|
|
|
new QueryWrapper<ErfExpApply>().eq("apply_no", applyNo)); |
|
|
|
if (apply == null) { |
|
|
|
throw new RuntimeException("申请单不存在: " + applyNo); |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 查询流程实例,从remark字段解析出下达时指定的全量审批人 |
|
|
|
// 节点实例是逐步创建的(如计划员节点在经理审批完成后才创建), |
|
|
|
// 因此必须从流程实例的remark中读取全量审批人,而非仅查当前"待审核"节点 |
|
|
|
ErfFlowInstance flowInstance = erfFlowInstanceMapper.selectOne( |
|
|
|
new QueryWrapper<ErfFlowInstance>().eq("apply_no", applyNo)); |
|
|
|
if (flowInstance == null) { |
|
|
|
throw new RuntimeException("申请单 " + applyNo + " 尚未提交审批流程"); |
|
|
|
} |
|
|
|
|
|
|
|
String approverJson = flowInstance.getRemark(); |
|
|
|
if (approverJson == null || approverJson.trim().isEmpty()) { |
|
|
|
throw new RuntimeException("申请单 " + applyNo + " 流程实例中没有审批人信息"); |
|
|
|
} |
|
|
|
|
|
|
|
// 3. 解析JSON获取全量审批人ID列表 |
|
|
|
long techManagerId; |
|
|
|
List<Long> prodManagerIds; |
|
|
|
List<Long> qualityManagerIds; |
|
|
|
List<Long> plannerIds; |
|
|
|
try { |
|
|
|
JsonNode root = JSON_MAPPER.readTree(approverJson); |
|
|
|
techManagerId = root.path("techManagerId").asLong(0); |
|
|
|
prodManagerIds = new ArrayList<>(); |
|
|
|
root.path("prodManagerIds").forEach(n -> prodManagerIds.add(n.asLong())); |
|
|
|
qualityManagerIds = new ArrayList<>(); |
|
|
|
root.path("qualityManagerIds").forEach(n -> qualityManagerIds.add(n.asLong())); |
|
|
|
plannerIds = new ArrayList<>(); |
|
|
|
root.path("plannerIds").forEach(n -> plannerIds.add(n.asLong())); |
|
|
|
} catch (Exception e) { |
|
|
|
throw new RuntimeException("解析审批人JSON失败: " + e.getMessage(), e); |
|
|
|
} |
|
|
|
|
|
|
|
log.info("申请单 {} 全量审批人 - 技术经理: {}, 生产经理: {}, 质量经理: {}, 计划员: {}", |
|
|
|
applyNo, techManagerId, prodManagerIds, qualityManagerIds, plannerIds); |
|
|
|
|
|
|
|
// 4. 查询该申请单已存在的节点实例,判断谁已完成 |
|
|
|
// "已批准"或"已完成"表示该人已经处理完毕,无需再催 |
|
|
|
List<ErfFlowNodeInstance> existingNodes = erfFlowNodeInstanceMapper.selectList( |
|
|
|
new QueryWrapper<ErfFlowNodeInstance>() |
|
|
|
.eq("apply_no", applyNo) |
|
|
|
.in("node_code", "技术经理审批", "生产经理审批", "质量经理审批", "计划员排产")); |
|
|
|
|
|
|
|
Set<Long> completedTechManagers = new HashSet<>(); |
|
|
|
Set<Long> completedProdManagers = new HashSet<>(); |
|
|
|
Set<Long> completedQualityManagers = new HashSet<>(); |
|
|
|
Set<Long> completedPlanners = new HashSet<>(); |
|
|
|
|
|
|
|
for (ErfFlowNodeInstance node : existingNodes) { |
|
|
|
if (node.getAssigneeUserId() == null) continue; |
|
|
|
boolean done = "已批准".equals(node.getStatus()) || "已完成".equals(node.getStatus()); |
|
|
|
if (!done) continue; |
|
|
|
switch (node.getNodeCode()) { |
|
|
|
case "技术经理审批": completedTechManagers.add(node.getAssigneeUserId()); break; |
|
|
|
case "生产经理审批": completedProdManagers.add(node.getAssigneeUserId()); break; |
|
|
|
case "质量经理审批": completedQualityManagers.add(node.getAssigneeUserId()); break; |
|
|
|
case "计划员排产": completedPlanners.add(node.getAssigneeUserId()); break; |
|
|
|
default: break; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 5. 查询当前实际"待审核"的节点,构建"userId -> 当前活跃节点编码集合"映射 |
|
|
|
// 必须按角色(节点编码)判断,而非仅凭userId,否则同一人担任多个角色时会误判 |
|
|
|
// 例:某人同时是技术经理和计划员,技术经理节点"待审核"时, |
|
|
|
// 若只用userId判断,其计划员催办邮件也会显示"当前正等待您处理",实际尚未轮到 |
|
|
|
String currentNodeCode = flowInstance.getCurrentNodeCode(); |
|
|
|
List<ErfFlowNodeInstance> activeNodes = existingNodes.stream() |
|
|
|
.filter(n -> "待审核".equals(n.getStatus())) |
|
|
|
.collect(Collectors.toList()); |
|
|
|
|
|
|
|
// 当前待处理人姓名(用于告知非当前步骤的收件人) |
|
|
|
String currentPendingNames = activeNodes.stream() |
|
|
|
.filter(n -> n.getAssigneeName() != null) |
|
|
|
.map(ErfFlowNodeInstance::getAssigneeName) |
|
|
|
.distinct() |
|
|
|
.collect(Collectors.joining("、")); |
|
|
|
|
|
|
|
// userId -> 该用户当前"待审核"的节点编码集合(精确到角色) |
|
|
|
Map<Long, Set<String>> activeNodesByUser = new HashMap<>(); |
|
|
|
for (ErfFlowNodeInstance node : activeNodes) { |
|
|
|
if (node.getAssigneeUserId() != null) { |
|
|
|
activeNodesByUser |
|
|
|
.computeIfAbsent(node.getAssigneeUserId(), k -> new HashSet<>()) |
|
|
|
.add(node.getNodeCode()); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
log.info("申请单 {} 当前节点: {}, 当前待处理人: {}", applyNo, currentNodeCode, currentPendingNames); |
|
|
|
|
|
|
|
// 6. 按角色分别判断 isCurrentStep,再发送催办邮件 |
|
|
|
// isCurrentStep = 该人在该角色对应的节点上有"待审核"记录 |
|
|
|
int sentCount = 0; |
|
|
|
|
|
|
|
// 技术经理 |
|
|
|
if (techManagerId != 0 && !completedTechManagers.contains(techManagerId)) { |
|
|
|
boolean isCurrent = activeNodesByUser |
|
|
|
.getOrDefault(techManagerId, Collections.emptySet()) |
|
|
|
.contains("技术经理审批"); |
|
|
|
if (sendManagerUrgeEmail(apply, techManagerId, currentNodeCode, currentPendingNames, isCurrent)) { |
|
|
|
sentCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
// 生产经理 |
|
|
|
for (Long id : prodManagerIds) { |
|
|
|
if (!completedProdManagers.contains(id)) { |
|
|
|
boolean isCurrent = activeNodesByUser |
|
|
|
.getOrDefault(id, Collections.emptySet()) |
|
|
|
.contains("生产经理审批"); |
|
|
|
if (sendManagerUrgeEmail(apply, id, currentNodeCode, currentPendingNames, isCurrent)) { |
|
|
|
sentCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 质量经理 |
|
|
|
for (Long id : qualityManagerIds) { |
|
|
|
if (!completedQualityManagers.contains(id)) { |
|
|
|
boolean isCurrent = activeNodesByUser |
|
|
|
.getOrDefault(id, Collections.emptySet()) |
|
|
|
.contains("质量经理审批"); |
|
|
|
if (sendManagerUrgeEmail(apply, id, currentNodeCode, currentPendingNames, isCurrent)) { |
|
|
|
sentCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
// 计划员 |
|
|
|
for (Long id : plannerIds) { |
|
|
|
if (!completedPlanners.contains(id)) { |
|
|
|
boolean isCurrent = activeNodesByUser |
|
|
|
.getOrDefault(id, Collections.emptySet()) |
|
|
|
.contains("计划员排产"); |
|
|
|
if (sendPlannerUrgeEmail(apply, id, currentNodeCode, currentPendingNames, isCurrent)) { |
|
|
|
sentCount++; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
log.info("=== 手动催办完成 === 申请单: {}, 成功发送邮件人数: {}", applyNo, sentCount); |
|
|
|
return sentCount; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 向经理(技术/生产/质量)发送催办邮件,链接指向审批待办页 |
|
|
|
* |
|
|
|
* @param apply 申请单信息 |
|
|
|
* @param managerId 收件经理用户ID |
|
|
|
* @param currentNodeCode 当前流程节点编码 |
|
|
|
* @param currentPendingNames 当前待处理人姓名(逗号分隔) |
|
|
|
* @param isCurrentStep 该经理是否就是当前步骤的待处理人 |
|
|
|
*/ |
|
|
|
private boolean sendManagerUrgeEmail(ErfExpApply apply, Long managerId, |
|
|
|
String currentNodeCode, String currentPendingNames, |
|
|
|
boolean isCurrentStep) { |
|
|
|
try { |
|
|
|
UserEmailInfoDto manager = sysUserDao.getUserEmailInfoById(managerId); |
|
|
|
if (manager == null) { |
|
|
|
log.warn("经理ID {} 不存在,跳过催办邮件", managerId); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (manager.getEmail() == null || manager.getEmail().trim().isEmpty()) { |
|
|
|
log.warn("经理 {} 未配置邮箱,跳过催办邮件", manager.getUsername()); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
String submitTimeStr = formatDate(apply.getSubmitTime()); |
|
|
|
String subject = String.format("【催办提醒】申请单 %s 等待您审批确认", apply.getApplyNo()); |
|
|
|
|
|
|
|
StringBuilder body = new StringBuilder(); |
|
|
|
body.append("<html><body style='font-family:Arial,sans-serif;color:#333;'>"); |
|
|
|
body.append("<h3 style='color:#E6A23C;'>⚠ 审批催办提醒</h3>"); |
|
|
|
body.append("<p>尊敬的 <b>").append(manager.getUsername()).append("</b>,您好!</p>"); |
|
|
|
|
|
|
|
if (isCurrentStep) { |
|
|
|
// 当前步骤就是该经理,需要立即处理 |
|
|
|
body.append("<p style='color:#F56C6C;font-weight:bold;'>🚨 当前正等待您完成审批,请尽快处理!</p>"); |
|
|
|
} else { |
|
|
|
// 非当前步骤,告知当前进度 |
|
|
|
body.append("<p>本申请单已向您发出催办,您的审批步骤尚未到达。</p>"); |
|
|
|
body.append(buildCurrentStepBlock(currentNodeCode, currentPendingNames)); |
|
|
|
} |
|
|
|
|
|
|
|
body.append("<hr style='border:1px solid #EBEEF5;'/>"); |
|
|
|
body.append(buildApplyInfoTable(apply, submitTimeStr)); |
|
|
|
body.append("<p>请点击以下链接前往审批:</p>"); |
|
|
|
body.append("<p><a style='color:#0f84b0;font-size:14px;' href='").append(APPROVAL_PAGE_URL) |
|
|
|
.append("'>前往审批待办页面</a></p>"); |
|
|
|
body.append(buildFooter()); |
|
|
|
body.append("</body></html>"); |
|
|
|
|
|
|
|
sendMail(subject, body.toString(), new String[]{manager.getEmail()}, "手动催办-经理"); |
|
|
|
log.info("已向经理 {} ({}) 发送催办邮件,是否当前步骤: {}", manager.getUsername(), manager.getEmail(), isCurrentStep); |
|
|
|
return true; |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("向经理ID {} 发送催办邮件失败: {}", managerId, e.getMessage(), e); |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 向计划员发送催办邮件,链接指向计划员排产页 |
|
|
|
* |
|
|
|
* @param apply 申请单信息 |
|
|
|
* @param plannerId 收件计划员用户ID |
|
|
|
* @param currentNodeCode 当前流程节点编码 |
|
|
|
* @param currentPendingNames 当前待处理人姓名(逗号分隔) |
|
|
|
* @param isCurrentStep 该计划员是否就是当前步骤的待处理人 |
|
|
|
*/ |
|
|
|
private boolean sendPlannerUrgeEmail(ErfExpApply apply, Long plannerId, |
|
|
|
String currentNodeCode, String currentPendingNames, |
|
|
|
boolean isCurrentStep) { |
|
|
|
try { |
|
|
|
UserEmailInfoDto planner = sysUserDao.getUserEmailInfoById(plannerId); |
|
|
|
if (planner == null) { |
|
|
|
log.warn("计划员ID {} 不存在,跳过催办邮件", plannerId); |
|
|
|
return false; |
|
|
|
} |
|
|
|
if (planner.getEmail() == null || planner.getEmail().trim().isEmpty()) { |
|
|
|
log.warn("计划员 {} 未配置邮箱,跳过催办邮件", planner.getUsername()); |
|
|
|
return false; |
|
|
|
} |
|
|
|
|
|
|
|
String submitTimeStr = formatDate(apply.getSubmitTime()); |
|
|
|
String subject = String.format("【催办提醒】申请单 %s 等待您安排排产", apply.getApplyNo()); |
|
|
|
|
|
|
|
StringBuilder body = new StringBuilder(); |
|
|
|
body.append("<html><body style='font-family:Arial,sans-serif;color:#333;'>"); |
|
|
|
body.append("<h3 style='color:#E6A23C;'>⚠ 排产催办提醒</h3>"); |
|
|
|
body.append("<p>尊敬的 <b>").append(planner.getUsername()).append("</b> 计划员,您好!</p>"); |
|
|
|
|
|
|
|
if (isCurrentStep) { |
|
|
|
body.append("<p style='color:#F56C6C;font-weight:bold;'>🚨 当前正等待您安排排产,请尽快处理!</p>"); |
|
|
|
} else { |
|
|
|
body.append("<p>本申请单已向您发出催办,排产步骤尚未到达(需经理审批完成后方可排产)。</p>"); |
|
|
|
body.append(buildCurrentStepBlock(currentNodeCode, currentPendingNames)); |
|
|
|
} |
|
|
|
|
|
|
|
body.append("<hr style='border:1px solid #EBEEF5;'/>"); |
|
|
|
body.append(buildApplyInfoTable(apply, submitTimeStr)); |
|
|
|
body.append("<p>请点击以下链接前往排产:</p>"); |
|
|
|
body.append("<p><a style='color:#0f84b0;font-size:14px;' href='").append(PLANNER_PAGE_URL) |
|
|
|
.append("'>前往计划员排产页面</a></p>"); |
|
|
|
body.append(buildFooter()); |
|
|
|
body.append("</body></html>"); |
|
|
|
|
|
|
|
sendMail(subject, body.toString(), new String[]{planner.getEmail()}, "手动催办-计划员"); |
|
|
|
log.info("已向计划员 {} ({}) 发送催办邮件,是否当前步骤: {}", planner.getUsername(), planner.getEmail(), isCurrentStep); |
|
|
|
return true; |
|
|
|
} catch (Exception e) { |
|
|
|
log.error("向计划员ID {} 发送催办邮件失败: {}", plannerId, e.getMessage(), e); |
|
|
|
return false; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 构建"当前流程进度"提示块(用于非当前步骤收件人) |
|
|
|
*/ |
|
|
|
private String buildCurrentStepBlock(String currentNodeCode, String currentPendingNames) { |
|
|
|
if (currentNodeCode == null || currentNodeCode.isEmpty()) { |
|
|
|
return ""; |
|
|
|
} |
|
|
|
String pendingPart = (currentPendingNames != null && !currentPendingNames.isEmpty()) |
|
|
|
? ",待处理人:<b>" + currentPendingNames + "</b>" |
|
|
|
: ""; |
|
|
|
return "<div style='margin:12px 0;padding:10px 14px;" |
|
|
|
+ "background:#FFF9F0;border-left:4px solid #E6A23C;border-radius:3px;'>" |
|
|
|
+ "<span style='color:#E6A23C;font-weight:bold;'>🕐 当前流程进度</span><br/>" |
|
|
|
+ "正在进行:<b>" + currentNodeCode + "</b>" + pendingPart |
|
|
|
+ "</div>"; |
|
|
|
} |
|
|
|
|
|
|
|
/** |
|
|
|
* 构建申请单信息HTML表格 |
|
|
|
*/ |
|
|
|
private String buildApplyInfoTable(ErfExpApply apply, String submitTimeStr) { |
|
|
|
return "<table border='1' cellpadding='8' cellspacing='0' style='border-collapse:collapse;width:100%;margin-bottom:16px;'>" |
|
|
|
+ "<thead style='background-color:#FDF6EC;'><tr>" |
|
|
|
+ "<th align='center'>申请单号</th>" |
|
|
|
+ "<th align='center'>事业部</th>" |
|
|
|
+ "<th align='center'>试验名称</th>" |
|
|
|
+ "<th align='center'>试验类型</th>" |
|
|
|
+ "<th align='center'>申请人</th>" |
|
|
|
+ "<th align='center'>下达时间</th>" |
|
|
|
+ "</tr></thead><tbody><tr>" |
|
|
|
+ "<td align='center'><b>" + apply.getApplyNo() + "</b></td>" |
|
|
|
+ "<td align='center'>" + nvl(apply.getBuNo()) + "</td>" |
|
|
|
+ "<td>" + nvl(apply.getTitle()) + "</td>" |
|
|
|
+ "<td align='center'>" + nvl(apply.getExperimentType()) + "</td>" |
|
|
|
+ "<td align='center'>" + nvl(apply.getCreatorName()) + "</td>" |
|
|
|
+ "<td align='center'>" + submitTimeStr + "</td>" |
|
|
|
+ "</tr></tbody></table>"; |
|
|
|
} |
|
|
|
|
|
|
|
private String buildFooter() { |
|
|
|
return "<p style='color:#888;font-size:12px;'>发送时间:" |
|
|
|
+ LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")) |
|
|
|
+ "</p>"; |
|
|
|
} |
|
|
|
|
|
|
|
private String formatDate(Date date) { |
|
|
|
if (date == null) return ""; |
|
|
|
return DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm") |
|
|
|
.format(date.toInstant().atZone(java.time.ZoneId.systemDefault()).toLocalDateTime()); |
|
|
|
} |
|
|
|
|
|
|
|
private String nvl(String s) { |
|
|
|
return s != null ? s : ""; |
|
|
|
} |
|
|
|
|
|
|
|
private void sendMail(String subject, String body, String[] toEmails, String mailType) { |
|
|
|
try { |
|
|
|
MailSendAddressData mailSendData = qcMapper.getSendMailFromAddress(); |
|
|
|
|