package com.sinata.appointment.service; import com.alibaba.excel.EasyExcel; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.metadata.IPage; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sinata.appointment.constants.AppointmentOrderStatus; import com.sinata.appointment.constants.ApprovalNodeCode; import com.sinata.appointment.constants.ApprovalTaskStatus; import com.sinata.appointment.dto.AppointmentAdminDetailVisitorVo; import com.sinata.appointment.dto.AppointmentAdminDetailVo; import com.sinata.appointment.dto.AppointmentAdminExportRow; import com.sinata.appointment.dto.AppointmentAdminListVo; import com.sinata.appointment.entity.AppointmentOrder; import com.sinata.appointment.entity.AppointmentVehicle; import com.sinata.appointment.entity.AppointmentVisitor; import com.sinata.appointment.entity.ApprovalInstance; import com.sinata.appointment.entity.ApprovalTask; import com.sinata.appointment.entity.BizVisitReason; import com.sinata.appointment.entity.MpUser; import com.sinata.appointment.entity.MpVisitor; import com.sinata.appointment.entity.SysDept; import com.sinata.appointment.exception.BizException; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.mapper.AppointmentOrderMapper; import com.sinata.appointment.mapper.AppointmentVehicleMapper; import com.sinata.appointment.mapper.AppointmentVisitorMapper; import com.sinata.appointment.mapper.ApprovalInstanceMapper; import com.sinata.appointment.mapper.ApprovalTaskMapper; import com.sinata.appointment.mapper.BizVisitReasonMapper; import com.sinata.appointment.mapper.MpUserMapper; import com.sinata.appointment.mapper.MpVisitorMapper; import com.sinata.appointment.mapper.SysDeptMapper; import com.sinata.appointment.util.MobileMaskUtil; import com.sinata.appointment.util.SensitiveDataUtil; import com.sinata.appointment.util.UuidAssert; import java.io.ByteArrayOutputStream; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.util.StringUtils; @Service @RequiredArgsConstructor public class AppointmentApplicationService { private static final String DISPLAY_STATUS_PENDING_VISIT = "PENDING_VISIT"; private static final String DISPLAY_STATUS_EFFECTIVE = "EFFECTIVE"; private static final String DISPLAY_STATUS_INVALID = "INVALID"; private static final TypeReference> STRING_LIST_TYPE = new TypeReference<>() {}; private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"); private final AppointmentOrderMapper appointmentOrderMapper; private final AppointmentVehicleMapper appointmentVehicleMapper; private final AppointmentVisitorMapper appointmentVisitorMapper; private final ApprovalInstanceMapper approvalInstanceMapper; private final ApprovalTaskMapper approvalTaskMapper; private final BizVisitReasonMapper bizVisitReasonMapper; private final MpUserMapper mpUserMapper; private final MpVisitorMapper mpVisitorMapper; private final SysDeptMapper sysDeptMapper; private final ObjectMapper objectMapper; public IPage pageOrders( long page, long size, String visitedUserName, Long visitedDeptId, String accessArea, String localUnit, LocalDate visitDateStart, LocalDate visitDateEnd, String displayStatus, String approvalStatus, String visitorKeyword, String visitReasonCode) { List all = listAdminOrders( visitedUserName, visitedDeptId, accessArea, localUnit, visitDateStart, visitDateEnd, displayStatus, approvalStatus, visitorKeyword, visitReasonCode); return slicePage(page, size, all); } public byte[] exportExcel( String visitedUserName, Long visitedDeptId, String accessArea, String localUnit, LocalDate visitDateStart, LocalDate visitDateEnd, String displayStatus, String approvalStatus, String visitorKeyword, String visitReasonCode) { Set visitorOrderIds = findOrderIdsByVisitorKeyword(visitorKeyword); if (visitorOrderIds != null && visitorOrderIds.isEmpty()) { return writeExportRows(List.of()); } List orders = appointmentOrderMapper.selectList( buildOrderQuery( visitedUserName, visitedDeptId, accessArea, localUnit, visitDateStart, visitDateEnd, visitReasonCode, visitorOrderIds)); if (orders.isEmpty()) { return writeExportRows(List.of()); } List orderIds = orders.stream().map(AppointmentOrder::getId).filter(Objects::nonNull).toList(); Map> visitorRowsMap = loadVisitorRows(orderIds); Map approvalInstanceMap = loadApprovalInstanceMap(orderIds); Map deptNameMap = loadDeptNameMap(orders); Map reasonNameMap = loadReasonNameMap(orders); List filteredOrders = new ArrayList<>(); for (AppointmentOrder order : orders) { AppointmentAdminListVo vo = toAdminListVo( order, deptNameMap.get(order.getVisitedDeptId()), reasonNameMap, visitorRowsMap.get(order.getId()), approvalInstanceMap.get(order.getId())); if (matchesDisplayStatus(vo, displayStatus) && matchesApprovalStatus(vo, approvalStatus)) { filteredOrders.add(order); } } List rows = buildExportRows(filteredOrders, visitorRowsMap, approvalInstanceMap, reasonNameMap); return writeExportRows(rows); } private byte[] writeExportRows(List rows) { try (ByteArrayOutputStream bos = new ByteArrayOutputStream()) { EasyExcel.write(bos, AppointmentAdminExportRow.class).sheet("预约记录").doWrite(rows); return bos.toByteArray(); } catch (Exception e) { return new byte[0]; } } public AppointmentOrder getById(Long id, String uuid) { AppointmentOrder o = appointmentOrderMapper.selectById(id); if (o == null || o.getDelFlag() != null && o.getDelFlag() == 1) { throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在"); } UuidAssert.match(o, uuid); return o; } public AppointmentAdminDetailVo detail(Long id, String uuid) { AppointmentOrder order = getById(id, uuid); String deptName = loadDeptNameMap(List.of(order)).get(order.getVisitedDeptId()); Map reasonNameMap = loadReasonNameMap(List.of(order)); List visitorRows = listVisitors(id); Map visitorMap = loadVisitorMap(visitorRows); Map plateMap = loadVisitorPlateMap(id); ApprovalInstance approvalInstance = loadLatestInstance(id); AppointmentAdminDetailVo vo = new AppointmentAdminDetailVo(); vo.setId(order.getId()); vo.setUuid(order.getUuid()); vo.setOrderNo(order.getOrderNo()); vo.setVisitTypeText(buildVisitTypeText(order)); vo.setVisitedUserName(defaultText(trimToNull(order.getVisitedUserName()))); vo.setVisitedDeptName(defaultText(trimToNull(deptName))); vo.setAccessAreaText(defaultText(buildJsonNameText(order.getPassAccessAreaNames()))); vo.setLocalUnitText(defaultText(buildJsonNameText(order.getPassLocalUnitNames()))); vo.setPlateNoText(defaultText(joinUniqueTexts(visitorRows, AppointmentVisitor::getPlateNo))); vo.setVisitReasonText(defaultText(buildVisitReasonText(order, reasonNameMap))); vo.setVisitStartText(defaultText(resolveVisitStartText(order))); vo.setVisitEndText(defaultText(resolveVisitEndText(order))); String displayStatusCode = resolveDisplayStatus(order); vo.setDisplayStatusCode(displayStatusCode); vo.setDisplayStatusText(resolveDisplayStatusText(displayStatusCode)); String approvalStatusCode = resolveApprovalStatusCode(order, approvalInstance); vo.setApprovalStatusCode(approvalStatusCode); vo.setApprovalStatusText(resolveApprovalStatusText(approvalStatusCode)); vo.setVisitors(buildDetailVisitors(visitorRows, visitorMap, plateMap)); return vo; } public List listVisitors(Long orderId) { return appointmentVisitorMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVisitor::getOrderId, orderId) .eq(AppointmentVisitor::getDelFlag, 0) .orderByAsc(AppointmentVisitor::getId)); } private List listAdminOrders( String visitedUserName, Long visitedDeptId, String accessArea, String localUnit, LocalDate visitDateStart, LocalDate visitDateEnd, String displayStatus, String approvalStatus, String visitorKeyword, String visitReasonCode) { Set visitorOrderIds = findOrderIdsByVisitorKeyword(visitorKeyword); if (visitorOrderIds != null && visitorOrderIds.isEmpty()) { return List.of(); } List orders = appointmentOrderMapper.selectList( buildOrderQuery( visitedUserName, visitedDeptId, accessArea, localUnit, visitDateStart, visitDateEnd, visitReasonCode, visitorOrderIds)); if (orders.isEmpty()) { return List.of(); } List orderIds = orders.stream().map(AppointmentOrder::getId).filter(Objects::nonNull).toList(); Map> visitorRowsMap = loadVisitorRows(orderIds); Map approvalInstanceMap = loadApprovalInstanceMap(orderIds); Map deptNameMap = loadDeptNameMap(orders); Map reasonNameMap = loadReasonNameMap(orders); List result = new ArrayList<>(); for (AppointmentOrder order : orders) { AppointmentAdminListVo vo = toAdminListVo( order, deptNameMap.get(order.getVisitedDeptId()), reasonNameMap, visitorRowsMap.get(order.getId()), approvalInstanceMap.get(order.getId())); if (matchesDisplayStatus(vo, displayStatus) && matchesApprovalStatus(vo, approvalStatus)) { result.add(vo); } } return result; } private LambdaQueryWrapper buildOrderQuery( String visitedUserName, Long visitedDeptId, String accessArea, String localUnit, LocalDate visitDateStart, LocalDate visitDateEnd, String visitReasonCode, Set visitorOrderIds) { LambdaQueryWrapper q = new LambdaQueryWrapper<>(); q.eq(AppointmentOrder::getDelFlag, 0); q.like(StringUtils.hasText(visitedUserName), AppointmentOrder::getVisitedUserName, trim(visitedUserName)); q.eq(visitedDeptId != null, AppointmentOrder::getVisitedDeptId, visitedDeptId); q.like(StringUtils.hasText(accessArea), AppointmentOrder::getPassAccessAreaNames, trim(accessArea)); q.like(StringUtils.hasText(localUnit), AppointmentOrder::getPassLocalUnitNames, trim(localUnit)); q.eq(StringUtils.hasText(visitReasonCode), AppointmentOrder::getVisitReasonCode, trim(visitReasonCode)); q.le(visitDateEnd != null, AppointmentOrder::getVisitStartDate, visitDateEnd); q.ge(visitDateStart != null, AppointmentOrder::getVisitEndDate, visitDateStart); if (visitorOrderIds != null) { q.in(AppointmentOrder::getId, visitorOrderIds); } q.orderByDesc(AppointmentOrder::getCreateTime).orderByDesc(AppointmentOrder::getId); return q; } private Set findOrderIdsByVisitorKeyword(String visitorKeyword) { String keyword = trimToNull(visitorKeyword); if (!StringUtils.hasText(keyword)) { return null; } List rows = appointmentVisitorMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVisitor::getDelFlag, 0) .like(AppointmentVisitor::getName, keyword) .select(AppointmentVisitor::getOrderId)); return rows.stream() .map(AppointmentVisitor::getOrderId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); } private Map loadVisitorMap(List rows) { if (rows == null || rows.isEmpty()) { return Map.of(); } Set visitorIds = rows.stream() .map(AppointmentVisitor::getMpVisitorId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (visitorIds.isEmpty()) { return Map.of(); } List visitors = mpVisitorMapper.selectList( new LambdaQueryWrapper() .eq(MpVisitor::getDelFlag, 0) .in(MpVisitor::getId, visitorIds)); return visitors.stream().collect(Collectors.toMap(MpVisitor::getId, v -> v, (a, b) -> a, LinkedHashMap::new)); } private Map loadVisitorPlateMap(Long orderId) { List rows = appointmentVehicleMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVehicle::getDelFlag, 0) .eq(AppointmentVehicle::getOrderId, orderId) .orderByAsc(AppointmentVehicle::getId)); if (rows.isEmpty()) { return Map.of(); } Map> plates = new LinkedHashMap<>(); for (AppointmentVehicle row : rows) { if (row.getDriverVisitorId() == null || !StringUtils.hasText(row.getPlateNo())) { continue; } plates.computeIfAbsent(row.getDriverVisitorId(), key -> new ArrayList<>()); plates.get(row.getDriverVisitorId()).add(row.getPlateNo().trim()); } Map result = new LinkedHashMap<>(); for (Map.Entry> entry : plates.entrySet()) { String joined = entry.getValue().stream() .filter(StringUtils::hasText) .map(String::trim) .distinct() .collect(Collectors.joining("、")); result.put(entry.getKey(), joined); } return result; } private Map> loadVisitorRows(List orderIds) { if (orderIds == null || orderIds.isEmpty()) { return Map.of(); } List rows = appointmentVisitorMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVisitor::getDelFlag, 0) .in(AppointmentVisitor::getOrderId, orderIds) .orderByAsc(AppointmentVisitor::getId)); return rows.stream() .collect(Collectors.groupingBy(AppointmentVisitor::getOrderId, LinkedHashMap::new, Collectors.toList())); } private Map loadApprovalInstanceMap(List orderIds) { if (orderIds == null || orderIds.isEmpty()) { return Map.of(); } List rows = approvalInstanceMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalInstance::getDelFlag, 0) .in(ApprovalInstance::getOrderId, orderIds) .orderByDesc(ApprovalInstance::getId)); Map result = new LinkedHashMap<>(); for (ApprovalInstance row : rows) { result.putIfAbsent(row.getOrderId(), row); } return result; } private ApprovalInstance loadLatestInstance(Long orderId) { List list = approvalInstanceMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalInstance::getDelFlag, 0) .eq(ApprovalInstance::getOrderId, orderId) .orderByDesc(ApprovalInstance::getId)); return list.isEmpty() ? null : list.get(0); } private Map loadDeptNameMap(List orders) { Set deptIds = orders.stream() .map(AppointmentOrder::getVisitedDeptId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (deptIds.isEmpty()) { return Map.of(); } List rows = sysDeptMapper.selectList( new LambdaQueryWrapper() .eq(SysDept::getDelFlag, 0) .in(SysDept::getId, deptIds)); return rows.stream() .collect(Collectors.toMap(SysDept::getId, SysDept::getDeptName, (a, b) -> a, LinkedHashMap::new)); } private Map loadReasonNameMap(List orders) { Set codes = orders.stream() .map(AppointmentOrder::getVisitReasonCode) .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.toCollection(LinkedHashSet::new)); if (codes.isEmpty()) { return Map.of(); } List rows = bizVisitReasonMapper.selectList( new LambdaQueryWrapper() .eq(BizVisitReason::getDelFlag, 0) .in(BizVisitReason::getReasonCode, codes)); return rows.stream() .collect( Collectors.toMap( BizVisitReason::getReasonCode, BizVisitReason::getReasonName, (a, b) -> a, LinkedHashMap::new)); } private AppointmentAdminListVo toAdminListVo( AppointmentOrder order, String deptName, Map reasonNameMap, List visitorRows, ApprovalInstance approvalInstance) { AppointmentAdminListVo vo = new AppointmentAdminListVo(); vo.setId(order.getId()); vo.setUuid(order.getUuid()); vo.setOrderNo(order.getOrderNo()); vo.setVisitTypeText(buildVisitTypeText(order)); vo.setVisitedUserName(defaultText(trimToNull(order.getVisitedUserName()))); vo.setVisitedDeptName(defaultText(trimToNull(deptName))); vo.setAccessAreaText(defaultText(buildJsonNameText(order.getPassAccessAreaNames()))); vo.setLocalUnitText(defaultText(buildJsonNameText(order.getPassLocalUnitNames()))); vo.setPlateNoText(defaultText(joinUniqueTexts(visitorRows, AppointmentVisitor::getPlateNo))); vo.setVisitReasonText(defaultText(buildVisitReasonText(order, reasonNameMap))); vo.setVisitTimeText(defaultText(buildVisitTimeText(order))); vo.setVisitorNamesText(defaultText(joinUniqueTexts(visitorRows, AppointmentVisitor::getName))); vo.setStatus(order.getStatus()); vo.setCreateTime(order.getCreateTime()); String displayStatusCode = resolveDisplayStatus(order); vo.setDisplayStatusCode(displayStatusCode); vo.setDisplayStatusText(resolveDisplayStatusText(displayStatusCode)); String approvalStatusCode = resolveApprovalStatusCode(order, approvalInstance); vo.setApprovalStatusCode(approvalStatusCode); vo.setApprovalStatusText(resolveApprovalStatusText(approvalStatusCode)); return vo; } private List buildExportRows( List orders, Map> visitorRowsMap, Map approvalInstanceMap, Map reasonNameMap) { if (orders == null || orders.isEmpty()) { return List.of(); } List orderIds = orders.stream().map(AppointmentOrder::getId).filter(Objects::nonNull).toList(); List allVisitorRows = orderIds.isEmpty() ? List.of() : visitorRowsMap.values().stream().flatMap(Collection::stream).toList(); Map visitorMap = loadVisitorMap(allVisitorRows); Map> visitorPlateMap = loadVisitorPlateMap(orderIds); Map> approvalNameMap = loadApprovalNodeApproverTextMap(approvalInstanceMap.values()); Map submitterMap = loadSubmitterMap(orders); List result = new ArrayList<>(); for (AppointmentOrder order : orders) { AppointmentAdminListVo summary = toAdminListVo( order, null, reasonNameMap, visitorRowsMap.get(order.getId()), approvalInstanceMap.get(order.getId())); Map nodeApproverNames = approvalNameMap.getOrDefault(order.getId(), Map.of()); MpUser submitter = submitterMap.get(order.getSubmitterId()); List visitors = visitorRowsMap.get(order.getId()); if (visitors == null || visitors.isEmpty()) { result.add(buildExportRow(summary, order, null, null, Map.of(), nodeApproverNames, submitter)); continue; } Map orderPlateMap = visitorPlateMap.getOrDefault(order.getId(), Map.of()); for (AppointmentVisitor visitorRow : visitors) { MpVisitor visitor = visitorMap.get(visitorRow.getMpVisitorId()); result.add(buildExportRow(summary, order, visitorRow, visitor, orderPlateMap, nodeApproverNames, submitter)); } } return result; } private AppointmentAdminExportRow buildExportRow( AppointmentAdminListVo summary, AppointmentOrder order, AppointmentVisitor visitorRow, MpVisitor visitor, Map orderPlateMap, Map nodeApproverNames, MpUser submitter) { AppointmentAdminExportRow row = new AppointmentAdminExportRow(); row.setVisitTypeText(summary.getVisitTypeText()); row.setVisitedUserName(summary.getVisitedUserName()); row.setAccessAreaText(summary.getAccessAreaText()); row.setLocalUnitText(summary.getLocalUnitText()); row.setPlateNoText( defaultText( firstNonBlank( visitorRow == null ? null : visitorRow.getPlateNo(), visitorRow == null ? null : orderPlateMap.get(visitorRow.getMpVisitorId()), summary.getPlateNoText()))); row.setVisitReasonText(summary.getVisitReasonText()); row.setVisitTimeText(summary.getVisitTimeText()); row.setDisplayStatusText(summary.getDisplayStatusText()); row.setVisitorLabel(defaultText(visitorRow == null ? null : trimToNull(visitorRow.getName()))); row.setVisitorName( defaultText( firstNonBlank( visitorRow == null ? null : visitorRow.getName(), visitor == null ? null : visitor.getVisitorName(), null))); row.setIdCardText(defaultText(resolveIdCardMasked(visitorRow, visitor))); row.setMobileText( defaultText( MobileMaskUtil.mask( firstNonBlank( visitorRow == null ? null : visitorRow.getMobile(), visitor == null ? null : visitor.getVisitorMobile(), null)))); row.setCompanyName(defaultText(visitor == null ? null : trimToNull(visitor.getVisitorCompany()))); row.setHandlerApproverNames(defaultText(nodeApproverNames.get(ApprovalNodeCode.LEVEL1_HANDLER))); row.setDeptLeaderApproverNames(defaultText(nodeApproverNames.get(ApprovalNodeCode.LEVEL2_DEPT_LEADER))); row.setVisitDeptApproverNames(defaultText(nodeApproverNames.get(ApprovalNodeCode.LEVEL4_VISIT_DEPT))); row.setVisitDeptLeaderApproverNames(defaultText(nodeApproverNames.get(ApprovalNodeCode.LEVEL5_VISIT_DEPT_LEADER))); row.setLocalDeptLeaderApproverNames(defaultText(nodeApproverNames.get(ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER))); row.setSubmitterMobile(defaultText(MobileMaskUtil.mask(submitter == null ? null : trimToNull(submitter.getMobile())))); row.setSubmitTimeText(defaultText(formatDateTime(order.getCreateTime()))); return row; } private Map loadSubmitterMap(List orders) { Set submitterIds = orders.stream() .map(AppointmentOrder::getSubmitterId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); if (submitterIds.isEmpty()) { return Map.of(); } List rows = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, submitterIds)); return rows.stream().collect(Collectors.toMap(MpUser::getId, u -> u, (a, b) -> a, LinkedHashMap::new)); } private Map> loadApprovalNodeApproverTextMap(Collection instances) { List instanceList = instances == null ? List.of() : instances.stream().filter(Objects::nonNull).toList(); if (instanceList.isEmpty()) { return Map.of(); } List instanceIds = instanceList.stream().map(ApprovalInstance::getId).filter(Objects::nonNull).toList(); if (instanceIds.isEmpty()) { return Map.of(); } List tasks = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getDelFlag, 0) .in(ApprovalTask::getInstanceId, instanceIds) .ne(ApprovalTask::getTaskStatus, ApprovalTaskStatus.SKIPPED) .orderByAsc(ApprovalTask::getId)); if (tasks.isEmpty()) { return Map.of(); } Set userIds = tasks.stream() .map(ApprovalTask::getAssigneeUserId) .filter(Objects::nonNull) .collect(Collectors.toCollection(LinkedHashSet::new)); Map userNameMap = loadMpUserNameMap(userIds); Map orderIdByInstanceId = instanceList.stream() .collect(Collectors.toMap(ApprovalInstance::getId, ApprovalInstance::getOrderId, (a, b) -> a, LinkedHashMap::new)); Map>> grouped = new LinkedHashMap<>(); for (ApprovalTask task : tasks) { Long orderId = orderIdByInstanceId.get(task.getInstanceId()); if (orderId == null || !StringUtils.hasText(task.getNodeCode())) { continue; } String userName = trimToNull(userNameMap.get(task.getAssigneeUserId())); if (!StringUtils.hasText(userName)) { continue; } grouped.computeIfAbsent(orderId, key -> new LinkedHashMap<>()); grouped.get(orderId).computeIfAbsent(task.getNodeCode(), key -> new LinkedHashSet<>()); grouped.get(orderId).get(task.getNodeCode()).add(userName); } Map> result = new LinkedHashMap<>(); for (Map.Entry>> orderEntry : grouped.entrySet()) { Map nodeTextMap = new LinkedHashMap<>(); for (Map.Entry> nodeEntry : orderEntry.getValue().entrySet()) { nodeTextMap.put(nodeEntry.getKey(), String.join("、", nodeEntry.getValue())); } result.put(orderEntry.getKey(), nodeTextMap); } return result; } private Map loadMpUserNameMap(Set userIds) { if (userIds == null || userIds.isEmpty()) { return Map.of(); } List rows = mpUserMapper.selectList( new LambdaQueryWrapper() .eq(MpUser::getDelFlag, 0) .in(MpUser::getId, userIds)); return rows.stream() .collect( Collectors.toMap( MpUser::getId, user -> firstNonBlank(user.getNickName(), user.getMobile(), String.valueOf(user.getId())), (a, b) -> a, LinkedHashMap::new)); } private Map> loadVisitorPlateMap(List orderIds) { if (orderIds == null || orderIds.isEmpty()) { return Map.of(); } List rows = appointmentVehicleMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVehicle::getDelFlag, 0) .in(AppointmentVehicle::getOrderId, orderIds) .orderByAsc(AppointmentVehicle::getId)); if (rows.isEmpty()) { return Map.of(); } Map>> temp = new LinkedHashMap<>(); for (AppointmentVehicle row : rows) { if (row.getOrderId() == null || row.getDriverVisitorId() == null || !StringUtils.hasText(row.getPlateNo())) { continue; } temp.computeIfAbsent(row.getOrderId(), key -> new LinkedHashMap<>()); temp.get(row.getOrderId()).computeIfAbsent(row.getDriverVisitorId(), key -> new ArrayList<>()); temp.get(row.getOrderId()).get(row.getDriverVisitorId()).add(row.getPlateNo().trim()); } Map> result = new LinkedHashMap<>(); for (Map.Entry>> orderEntry : temp.entrySet()) { Map visitorPlateTextMap = new LinkedHashMap<>(); for (Map.Entry> visitorEntry : orderEntry.getValue().entrySet()) { String joined = visitorEntry.getValue().stream() .filter(StringUtils::hasText) .map(String::trim) .distinct() .collect(Collectors.joining("、")); visitorPlateTextMap.put(visitorEntry.getKey(), joined); } result.put(orderEntry.getKey(), visitorPlateTextMap); } return result; } private String resolveIdCardMasked(AppointmentVisitor row, MpVisitor visitor) { String idCardPlain = row != null && StringUtils.hasText(row.getIdCardCipher()) ? SensitiveDataUtil.decryptIdCard(row.getIdCardCipher()) : (visitor != null && StringUtils.hasText(visitor.getIdCardCipher()) ? SensitiveDataUtil.decryptIdCard(visitor.getIdCardCipher()) : null); return StringUtils.hasText(idCardPlain) ? SensitiveDataUtil.maskIdCard(idCardPlain) : null; } private String formatDateTime(LocalDateTime value) { return value == null ? null : value.format(DATE_TIME_FORMATTER); } private List buildDetailVisitors( List rows, Map visitorMap, Map plateMap) { if (rows == null || rows.isEmpty()) { return List.of(); } List result = new ArrayList<>(); for (AppointmentVisitor row : rows) { MpVisitor visitor = visitorMap.get(row.getMpVisitorId()); AppointmentAdminDetailVisitorVo vo = new AppointmentAdminDetailVisitorVo(); vo.setVisitorId(row.getMpVisitorId()); vo.setName(firstNonBlank(row.getName(), visitor == null ? null : visitor.getVisitorName(), "-")); vo.setGender(defaultText(visitor == null ? null : trimToNull(visitor.getGender()))); vo.setCompanyName(defaultText(visitor == null ? null : trimToNull(visitor.getVisitorCompany()))); vo.setMobileMasked( defaultText( MobileMaskUtil.mask( firstNonBlank(row.getMobile(), visitor == null ? null : visitor.getVisitorMobile(), null)))); String idCardPlain = StringUtils.hasText(row.getIdCardCipher()) ? SensitiveDataUtil.decryptIdCard(row.getIdCardCipher()) : (visitor != null && StringUtils.hasText(visitor.getIdCardCipher()) ? SensitiveDataUtil.decryptIdCard(visitor.getIdCardCipher()) : null); vo.setIdCardMasked( defaultText( StringUtils.hasText(idCardPlain) ? SensitiveDataUtil.maskIdCard(idCardPlain) : null)); vo.setPlateNo(defaultText(firstNonBlank(row.getPlateNo(), plateMap.get(row.getMpVisitorId()), null))); vo.setDriverName(defaultText(trimToNull(row.getDriverName()))); result.add(vo); } return result; } private boolean matchesDisplayStatus(AppointmentAdminListVo row, String filter) { String value = trimToNull(filter); return !StringUtils.hasText(value) || Objects.equals(value, row.getDisplayStatusCode()); } private boolean matchesApprovalStatus(AppointmentAdminListVo row, String filter) { String value = trimToNull(filter); return !StringUtils.hasText(value) || Objects.equals(value, row.getApprovalStatusCode()); } private String buildVisitTypeText(AppointmentOrder order) { boolean personEntry = order.getPersonEntryFlag() == null || Boolean.TRUE.equals(order.getPersonEntryFlag()); boolean vehicleEntry = Boolean.TRUE.equals(order.getVehicleEntryFlag()); if (personEntry && vehicleEntry) { return "访客+车辆预约"; } if (vehicleEntry) { return "车辆预约"; } return "访客预约"; } private String buildVisitReasonText(AppointmentOrder row, Map reasonNameMap) { String code = trimToNull(row.getVisitReasonCode()); if (!StringUtils.hasText(code)) { return null; } if ("OTHER".equalsIgnoreCase(code)) { String other = trimToNull(row.getVisitReasonOther()); return StringUtils.hasText(other) ? "其他-" + other : "其他"; } String reasonName = reasonNameMap.get(code); return StringUtils.hasText(reasonName) ? reasonName : code; } private String buildVisitTimeText(AppointmentOrder row) { if (row.getVisitStartDate() == null && row.getVisitEndDate() == null) { return null; } if (Objects.equals(row.getVisitStartDate(), row.getVisitEndDate()) || row.getVisitEndDate() == null) { return String.valueOf(row.getVisitStartDate()); } return row.getVisitStartDate() + " 至 " + row.getVisitEndDate(); } private String resolveVisitStartText(AppointmentOrder row) { return row.getVisitStartDate() == null ? null : row.getVisitStartDate().toString(); } private String resolveVisitEndText(AppointmentOrder row) { LocalDate end = row.getVisitEndDate() != null ? row.getVisitEndDate() : row.getVisitStartDate(); return end == null ? null : end.toString(); } private String resolveDisplayStatus(AppointmentOrder row) { LocalDate today = LocalDate.now(); if (AppointmentOrderStatus.PENDING_APPROVAL.equals(row.getStatus())) { return DISPLAY_STATUS_PENDING_VISIT; } if (AppointmentOrderStatus.APPROVED.equals(row.getStatus())) { if (row.getVisitStartDate() != null && today.isBefore(row.getVisitStartDate())) { return DISPLAY_STATUS_PENDING_VISIT; } if (row.getVisitStartDate() != null && row.getVisitEndDate() != null && (today.isEqual(row.getVisitStartDate()) || today.isAfter(row.getVisitStartDate())) && (today.isEqual(row.getVisitEndDate()) || today.isBefore(row.getVisitEndDate()))) { return DISPLAY_STATUS_EFFECTIVE; } } return DISPLAY_STATUS_INVALID; } private String resolveDisplayStatusText(String code) { return switch (trimToNull(code)) { case DISPLAY_STATUS_PENDING_VISIT -> "待拜访"; case DISPLAY_STATUS_EFFECTIVE -> "生效中"; default -> "已失效"; }; } private String resolveApprovalStatusCode(AppointmentOrder order, ApprovalInstance approvalInstance) { String status = trimToNull(order.getStatus()); if (AppointmentOrderStatus.PENDING_APPROVAL.equals(status)) { String nodeCode = approvalInstance == null ? null : trimToNull(approvalInstance.getCurrentNodeCode()); return StringUtils.hasText(nodeCode) ? nodeCode : AppointmentOrderStatus.PENDING_APPROVAL; } if (AppointmentOrderStatus.APPROVED.equals(status) || AppointmentOrderStatus.VISITED.equals(status) || AppointmentOrderStatus.EXPIRED.equals(status)) { return AppointmentOrderStatus.APPROVED; } if (AppointmentOrderStatus.REJECTED.equals(status)) { return AppointmentOrderStatus.REJECTED; } if (AppointmentOrderStatus.CANCELLED.equals(status)) { return AppointmentOrderStatus.CANCELLED; } return status; } private String resolveApprovalStatusText(String code) { return switch (trimToNull(code)) { 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 -> "主管部门领导"; case AppointmentOrderStatus.APPROVED -> "已通过"; case AppointmentOrderStatus.REJECTED -> "已拒绝"; case AppointmentOrderStatus.CANCELLED -> "已取消"; case AppointmentOrderStatus.PENDING_APPROVAL -> "审批中"; default -> "-"; }; } private String buildJsonNameText(String rawJson) { String raw = trimToNull(rawJson); if (!StringUtils.hasText(raw)) { return null; } try { List rows = objectMapper.readValue(raw, STRING_LIST_TYPE); return rows.stream() .filter(StringUtils::hasText) .map(String::trim) .distinct() .collect(Collectors.joining("、")); } catch (Exception ignore) { return raw; } } private String joinUniqueTexts(List rows, java.util.function.Function getter) { if (rows == null || rows.isEmpty()) { return null; } return rows.stream() .map(getter) .filter(StringUtils::hasText) .map(String::trim) .distinct() .collect(Collectors.joining("、")); } private IPage slicePage(long page, long size, List rowsAll) { long current = page <= 0 ? 1 : page; long pageSize = size <= 0 ? 10 : size; int from = (int) ((current - 1) * pageSize); int to = Math.min(rowsAll.size(), from + (int) pageSize); List records = from >= to ? List.of() : rowsAll.subList(from, to); Page out = new Page<>(current, pageSize); out.setTotal(rowsAll.size()); out.setRecords(records); return out; } private static String trim(String value) { return value == null ? null : value.trim(); } private static String trimToNull(String value) { String trimmed = trim(value); return StringUtils.hasText(trimmed) ? trimmed : null; } private static String defaultText(String value) { return StringUtils.hasText(value) ? value : "-"; } @SafeVarargs private static String firstNonBlank(String... values) { if (values == null) { return null; } for (String value : values) { if (StringUtils.hasText(value)) { return value.trim(); } } return null; } }