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.scheduling.annotation.Async;
|
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 static final DateTimeFormatter TEMPLATE_TIME_FORMATTER =
|
DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm");
|
|
private final WechatMiniGateway wechatMiniGateway;
|
private final MpUserMapper mpUserMapper;
|
private final ApprovalTaskMapper approvalTaskMapper;
|
private final BizVisitReasonMapper bizVisitReasonMapper;
|
|
@Async
|
public void notifyApprovalFinalResult(AppointmentOrder order, boolean approved) {
|
try {
|
if (order == null || order.getSubmitterId() == null) {
|
log.info("Mini subscribe final result skipped, order or submitter empty. orderId={}",
|
order == null ? null : order.getId());
|
return;
|
}
|
MpUser submitter =
|
mpUserMapper.selectOne(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.eq(MpUser::getId, order.getSubmitterId())
|
.last("limit 1"));
|
if (submitter == null || !StringUtils.hasText(submitter.getOpenId())) {
|
log.info(
|
"Mini subscribe final result skipped, submitter not found or openId empty. submitterId={} orderNo={}",
|
order.getSubmitterId(),
|
order.getOrderNo());
|
return;
|
}
|
Map<String, String> templateData = new LinkedHashMap<>();
|
templateData.put("thing1", approved ? "审核通过" : "审核未通过");
|
templateData.put("thing2", abbreviate(resolveSubmitterName(submitter), 20));
|
templateData.put("time3", buildVisitTemplateTimeValue(order));
|
templateData.put("thing4", abbreviate(buildVisitReasonText(order), 20));
|
wechatMiniGateway.sendSubscribeMessage(
|
submitter.getOpenId(), APPROVAL_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData);
|
log.info(
|
"Mini subscribe final result success. submitterId={} orderNo={} result={}",
|
order.getSubmitterId(),
|
order.getOrderNo(),
|
approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED);
|
} catch (Exception ex) {
|
log.warn("Mini subscribe final result failed. orderId={} orderNo={}",
|
order == null ? null : order.getId(), order == null ? null : order.getOrderNo(), ex);
|
}
|
}
|
|
@Async
|
public void notifyApprovalNoticeUsers(
|
AppointmentOrder order, boolean approved, java.util.Collection<MpUser> noticeUsers, int visitorCount) {
|
try {
|
if (order == null || noticeUsers == null || noticeUsers.isEmpty()) {
|
log.info("Mini subscribe notice skipped, order or notice users empty. orderId={}",
|
order == null ? null : order.getId());
|
return;
|
}
|
Map<String, String> templateData = new LinkedHashMap<>();
|
templateData.put("thing6", buildVisitorCountText(visitorCount));
|
templateData.put("time18", buildVisitTemplateTimeValue(order));
|
templateData.put("thing20", abbreviate(buildNoticeBusinessTypeText(order, approved), 20));
|
int successCount = 0;
|
int skippedCount = 0;
|
int failedCount = 0;
|
for (MpUser noticeUser : noticeUsers) {
|
if (noticeUser == null || !StringUtils.hasText(noticeUser.getOpenId())) {
|
skippedCount++;
|
continue;
|
}
|
try {
|
wechatMiniGateway.sendSubscribeMessage(
|
noticeUser.getOpenId(), NOTICE_RESULT_TEMPLATE_ID, APPROVAL_RESULT_PAGE, templateData);
|
successCount++;
|
log.info(
|
"Mini subscribe notice success. noticeUserId={} orderNo={} result={}",
|
noticeUser.getId(),
|
order.getOrderNo(),
|
approved ? AppointmentOrderStatus.APPROVED : AppointmentOrderStatus.REJECTED);
|
} catch (Exception ex) {
|
failedCount++;
|
log.warn("Mini subscribe notice failed. noticeUserId={} orderId={} orderNo={}",
|
noticeUser.getId(), order.getId(), order.getOrderNo(), ex);
|
}
|
}
|
log.info("Mini subscribe notice completed. orderId={} orderNo={} success={} skipped={} failed={}",
|
order.getId(), order.getOrderNo(), successCount, skippedCount, failedCount);
|
} catch (Exception ex) {
|
log.warn("Mini subscribe notice batch failed. orderId={} orderNo={}",
|
order == null ? null : order.getId(), order == null ? null : order.getOrderNo(), ex);
|
}
|
}
|
|
@Async
|
public void notifyCurrentApprovers(AppointmentOrder order, Long instanceId) {
|
try {
|
if (order == null || instanceId == null) {
|
log.info("Mini subscribe todo skipped, order or instance empty. orderId={} instanceId={}",
|
order == null ? null : order.getId(), instanceId);
|
return;
|
}
|
java.util.List<ApprovalTask> pendingTasks =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.eq(ApprovalTask::getDelFlag, 0)
|
.eq(ApprovalTask::getInstanceId, instanceId)
|
.eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)
|
.orderByAsc(ApprovalTask::getId));
|
if (pendingTasks.isEmpty()) {
|
log.info("Mini subscribe todo skipped, no pending tasks. orderId={} instanceId={}",
|
order.getId(), instanceId);
|
return;
|
}
|
java.util.LinkedHashMap<Long, java.util.LinkedHashSet<String>> 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()) {
|
log.info("Mini subscribe todo skipped, no assignee users. orderId={} instanceId={}",
|
order.getId(), instanceId);
|
return;
|
}
|
java.util.List<MpUser> users =
|
mpUserMapper.selectList(
|
new LambdaQueryWrapper<MpUser>()
|
.eq(MpUser::getDelFlag, 0)
|
.in(MpUser::getId, userNodeNames.keySet()));
|
java.util.Map<Long, MpUser> userMap = new java.util.LinkedHashMap<>();
|
for (MpUser user : users) {
|
userMap.putIfAbsent(user.getId(), user);
|
}
|
int successCount = 0;
|
int skippedCount = 0;
|
int failedCount = 0;
|
for (java.util.Map.Entry<Long, java.util.LinkedHashSet<String>> entry : userNodeNames.entrySet()) {
|
MpUser user = userMap.get(entry.getKey());
|
if (user == null || !StringUtils.hasText(user.getOpenId())) {
|
skippedCount++;
|
continue;
|
}
|
Map<String, String> templateData = new LinkedHashMap<>();
|
templateData.put("thing1", abbreviate(buildTodoTaskName(order), 20));
|
templateData.put("thing3", abbreviate(String.join("、", entry.getValue()), 20));
|
try {
|
wechatMiniGateway.sendSubscribeMessage(
|
user.getOpenId(), APPROVAL_TODO_TEMPLATE_ID, APPROVAL_TODO_PAGE, templateData);
|
successCount++;
|
log.info(
|
"Mini subscribe todo success. approverId={} orderNo={} stages={}",
|
user.getId(),
|
order.getOrderNo(),
|
entry.getValue());
|
} catch (Exception ex) {
|
failedCount++;
|
log.warn("Mini subscribe todo failed. approverId={} orderId={} orderNo={} stages={}",
|
user.getId(), order.getId(), order.getOrderNo(), entry.getValue(), ex);
|
}
|
}
|
log.info("Mini subscribe todo completed. orderId={} orderNo={} success={} skipped={} failed={}",
|
order.getId(), order.getOrderNo(), successCount, skippedCount, failedCount);
|
} catch (Exception ex) {
|
log.warn("Mini subscribe todo batch failed. orderId={} instanceId={}",
|
order == null ? null : order.getId(), instanceId, ex);
|
}
|
}
|
|
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 buildVisitTemplateTimeValue(AppointmentOrder order) {
|
LocalDate start = order.getVisitStartDate();
|
LocalDate end = order.getVisitEndDate();
|
if (start == null && end == null) {
|
return "2099-12-31 00:00";
|
}
|
if (start == null) {
|
start = end;
|
}
|
if (end == null) {
|
end = start;
|
}
|
return switch (safeTrim(order.getVisitTimeMode())) {
|
case "MULTI" -> start.atTime(0, 0).format(TEMPLATE_TIME_FORMATTER);
|
default -> start.atTime(resolveTemplateHour(order.getVisitTimeSlot()), 0)
|
.format(TEMPLATE_TIME_FORMATTER);
|
};
|
}
|
|
private String buildVisitReasonText(AppointmentOrder order) {
|
String reasonCode = safeTrim(order.getVisitReasonCode());
|
if (!StringUtils.hasText(reasonCode)) {
|
return "拜访";
|
}
|
if (AppointmentBizConstants.VISIT_REASON_OTHER.equalsIgnoreCase(reasonCode)) {
|
return "其他";
|
}
|
BizVisitReason reason =
|
bizVisitReasonMapper.selectOne(
|
new LambdaQueryWrapper<BizVisitReason>()
|
.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 int resolveTemplateHour(String slot) {
|
return switch (safeTrim(slot)) {
|
case "AM" -> 9;
|
case "PM" -> 14;
|
case "ALL_DAY" -> 9;
|
default -> 9;
|
};
|
}
|
|
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();
|
}
|
}
|