package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.sinata.appointment.constants.AppointmentBizConstants; import com.sinata.appointment.constants.AppointmentOrderStatus; import com.sinata.appointment.constants.ApprovalNodeCode; import com.sinata.appointment.constants.ApprovalTaskStatus; import com.sinata.appointment.entity.ApprovalTask; import com.sinata.appointment.entity.AppointmentOrder; import com.sinata.appointment.entity.BizVisitReason; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.integration.WechatMiniGateway; import com.sinata.appointment.mapper.ApprovalTaskMapper; import com.sinata.appointment.mapper.BizVisitReasonMapper; import com.sinata.appointment.mapper.MpUserMapper; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.LinkedHashMap; import java.util.Map; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; /** * 小程序订阅消息发送。 */ @Slf4j @Service @RequiredArgsConstructor public class IntegrationWechatService { private static final String APPROVAL_RESULT_TEMPLATE_ID = "4FQovCBNFM7rth42AWHTusHajO788eT9VpplSN4ZG2s"; private static final String NOTICE_RESULT_TEMPLATE_ID = "GC1S3T2qHCbz8ow130u7elRczuqW6Z8q54mKRCYE-xo"; private static final String APPROVAL_RESULT_PAGE = "pages/record/index"; private static final String APPROVAL_TODO_TEMPLATE_ID = "BX49OBHvRcoYiCw0ZLqx04BiWQ9KaEQwuVth_Lh_M2A"; private static final String APPROVAL_TODO_PAGE = "pages/reviewList/index"; private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd"); private final WechatMiniGateway wechatMiniGateway; private final MpUserMapper mpUserMapper; private final ApprovalTaskMapper approvalTaskMapper; private final BizVisitReasonMapper bizVisitReasonMapper; public void notifyApprovalFinalResult(AppointmentOrder order, boolean approved) { if (order == null || order.getSubmitterId() == null) { return; } MpUser submitter = mpUserMapper.selectOne( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .eq(MpUser::getId, order.getSubmitterId()) .last("limit 1")); if (submitter == null || !StringUtils.hasText(submitter.getOpenId())) { log.info( "Skip mini subscribe notify, submitter not found or openId empty. submitterId={} orderNo={}", order.getSubmitterId(), order.getOrderNo()); return; } Map templateData = new LinkedHashMap<>(); templateData.put("thing1", approved ? "审核通过" : "审核未通过"); templateData.put("thing2", abbreviate(resolveSubmitterName(submitter), 20)); templateData.put("time3", buildVisitTimeText(order)); templateData.put("thing4", abbreviate(buildVisitReasonText(order), 20)); wechatMiniGateway.sendSubscribeMessage( submitter.getOpenId(), APPROVAL_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData); log.info( "Mini subscribe notify sent submitterId={} orderNo={} result={}", order.getSubmitterId(), order.getOrderNo(), approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED); } public void notifyApprovalNoticeUsers( AppointmentOrder order, boolean approved, java.util.Collection noticeUsers, int visitorCount) { if (order == null || noticeUsers == null || noticeUsers.isEmpty()) { return; } Map templateData = new LinkedHashMap<>(); templateData.put("thing6", buildVisitorCountText(visitorCount)); templateData.put("time18", buildVisitTimeText(order)); templateData.put("thing20", abbreviate(buildNoticeBusinessTypeText(order, approved), 20)); for (MpUser noticeUser : noticeUsers) { if (noticeUser == null || !StringUtils.hasText(noticeUser.getOpenId())) { continue; } wechatMiniGateway.sendSubscribeMessage( noticeUser.getOpenId(), NOTICE_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData); log.info( "Mini subscribe notice sent noticeUserId={} orderNo={} result={}", noticeUser.getId(), order.getOrderNo(), approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED); } } public void notifyCurrentApprovers(AppointmentOrder order, Long instanceId) { if (order == null || instanceId == null) { return; } java.util.List pendingTasks = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getDelFlag, 0) .eq(ApprovalTask::getInstanceId, instanceId) .eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING) .orderByAsc(ApprovalTask::getId)); if (pendingTasks.isEmpty()) { return; } java.util.LinkedHashMap> userNodeNames = new java.util.LinkedHashMap<>(); for (ApprovalTask task : pendingTasks) { if (task.getAssigneeUserId() == null) { continue; } userNodeNames.computeIfAbsent(task.getAssigneeUserId(), key -> new java.util.LinkedHashSet<>()); userNodeNames.get(task.getAssigneeUserId()).add(resolveNodeName(task.getNodeCode())); } if (userNodeNames.isEmpty()) { return; } java.util.List users = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, userNodeNames.keySet())); java.util.Map userMap = new java.util.LinkedHashMap<>(); for (MpUser user : users) { userMap.putIfAbsent(user.getId(), user); } for (java.util.Map.Entry> entry : userNodeNames.entrySet()) { MpUser user = userMap.get(entry.getKey()); if (user == null || !StringUtils.hasText(user.getOpenId())) { continue; } Map templateData = new LinkedHashMap<>(); templateData.put("thing1", abbreviate(buildTodoTaskName(order), 20)); templateData.put("thing3", abbreviate(String.join("、", entry.getValue()), 20)); wechatMiniGateway.sendSubscribeMessage( user.getOpenId(), APPROVAL_TODO_TEMPLATE_ID, APPROVAL_TODO_PAGE, templateData); log.info( "Mini subscribe todo notify sent approverId={} orderNo={} stages={}", user.getId(), order.getOrderNo(), entry.getValue()); } } private String resolveSubmitterName(MpUser submitter) { if (StringUtils.hasText(submitter.getNickName())) { return submitter.getNickName().trim(); } if (StringUtils.hasText(submitter.getMobile())) { return submitter.getMobile().trim(); } return "预约人"; } private String buildVisitTimeText(AppointmentOrder order) { LocalDate start = order.getVisitStartDate(); LocalDate end = order.getVisitEndDate(); if (start == null && end == null) { return "待定"; } if (start == null) { start = end; } if (end == null) { end = start; } return switch (safeTrim(order.getVisitTimeMode())) { case "MULTI" -> formatDate(start); default -> formatDate(start) + " " + mapTimeSlotLabel(order.getVisitTimeSlot()); }; } private String buildVisitReasonText(AppointmentOrder order) { String reasonCode = safeTrim(order.getVisitReasonCode()); if (!StringUtils.hasText(reasonCode)) { return "拜访"; } if (AppointmentBizConstants.VISIT_REASON_OTHER.equalsIgnoreCase(reasonCode)) { String other = safeTrim(order.getVisitReasonOther()); return StringUtils.hasText(other) ? other : "其他"; } BizVisitReason reason = bizVisitReasonMapper.selectOne( new LambdaQueryWrapper() .eq(BizVisitReason::getDelFlag, 0) .eq(BizVisitReason::getReasonCode, reasonCode) .last("limit 1")); if (reason != null && StringUtils.hasText(reason.getReasonName())) { return reason.getReasonName().trim(); } return reasonCode; } private String buildVisitorCountText(int visitorCount) { return Math.max(visitorCount, 0) + "人"; } private String buildNoticeBusinessTypeText(AppointmentOrder order, boolean approved) { String reason = buildVisitReasonText(order); if (!StringUtils.hasText(reason)) { return approved ? "预约通过" : "预约驳回"; } return reason; } private String buildTodoTaskName(AppointmentOrder order) { if (order == null || !StringUtils.hasText(order.getOrderNo())) { return "预约审核"; } return "预约审核-" + order.getOrderNo(); } private String resolveNodeName(String nodeCode) { return switch (safeTrim(nodeCode)) { case ApprovalNodeCode.LEVEL1_HANDLER -> "经办人(一级)"; case ApprovalNodeCode.LEVEL2_DEPT_LEADER -> "部门负责人(二级)"; case ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER -> "属地管理部门领导(三级)"; case ApprovalNodeCode.LEVEL4_VISIT_DEPT -> "访客主管部门(四级)"; case ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER -> "访客主管部门领导(五级)"; default -> "审批待办"; }; } private String mapTimeSlotLabel(String slot) { return switch (safeTrim(slot)) { case "AM" -> "上午"; case "PM" -> "下午"; case "ALL_DAY" -> "全天"; default -> "全天"; }; } private String formatDate(LocalDate date) { return date.format(DATE_FORMATTER); } private String abbreviate(String value, int maxLength) { if (!StringUtils.hasText(value)) { return ""; } String trimmed = value.trim(); if (trimmed.length() <= maxLength) { return trimmed; } return trimmed.substring(0, maxLength); } private String safeTrim(String value) { if (!StringUtils.hasText(value)) { return ""; } return value.trim(); } }