package com.sinata.appointment.service; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; import com.sinata.appointment.config.MiniStpUtil; import com.sinata.appointment.constants.AppointmentBizConstants; import com.sinata.appointment.constants.ApprovalNodeCode; import com.sinata.appointment.constants.ApprovalTaskStatus; import com.sinata.appointment.constants.BizErrorCode; import com.sinata.appointment.dto.ApprovalFlowNodeVo; import com.sinata.appointment.dto.MiniApprovalDetailVisitorVo; import com.sinata.appointment.dto.MiniApprovalDetailVo; import com.sinata.appointment.dto.MiniLocalUnitVo; 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.MpPassLocalUnit; import com.sinata.appointment.entity.MpVisitor; import com.sinata.appointment.entity.SysDept; import com.sinata.appointment.exception.BizException; 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.MpPassLocalUnitMapper; 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.time.LocalDate; import java.util.ArrayList; 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 MiniApprovalDetailApplicationService { private static final String PAGE_MODE_HANDLER_PENDING = "HANDLER_PENDING"; private static final String PAGE_MODE_GENERAL_PENDING = "GENERAL_PENDING"; private static final String PAGE_MODE_GENERAL_READONLY = "GENERAL_READONLY"; private static final TypeReference> LONG_LIST_TYPE = new TypeReference<>() {}; private static final TypeReference> STRING_LIST_TYPE = new TypeReference<>() {}; private final ApprovalTaskMapper approvalTaskMapper; private final ApprovalInstanceMapper approvalInstanceMapper; private final AppointmentOrderMapper appointmentOrderMapper; private final AppointmentVisitorMapper appointmentVisitorMapper; private final AppointmentVehicleMapper appointmentVehicleMapper; private final MpVisitorMapper mpVisitorMapper; private final SysDeptMapper sysDeptMapper; private final BizVisitReasonMapper bizVisitReasonMapper; private final MpPassLocalUnitMapper mpPassLocalUnitMapper; private final ApprovalFlowViewApplicationService approvalFlowViewApplicationService; private final ObjectMapper objectMapper; public MiniApprovalDetailVo detail(Long taskId, String taskUuid, Long orderId, String orderUuid) { long uid = MiniStpUtil.getLoginIdAsLong(); ApprovalTask task = null; if (taskId != null && taskId > 0) { task = approvalTaskMapper.selectById(taskId); if (task == null || (task.getDelFlag() != null && task.getDelFlag() == 1)) { throw new BizException(BizErrorCode.APPROVAL_TASK_NOT_FOUND, "审批任务不存在"); } UuidAssert.match(task, taskUuid); } ApprovalInstance instance = loadInstance(task, orderId); AppointmentOrder order = loadOrder(instance, orderId, orderUuid); ApprovalTask currentUserTask = resolveCurrentUserTask(task, uid, instance, order); if (currentUserTask == null && !Objects.equals(order.getVisitedUserId(), uid)) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "无权查看该审批详情"); } MiniApprovalDetailVo vo = new MiniApprovalDetailVo(); vo.setTaskId(currentUserTask == null ? null : currentUserTask.getId()); vo.setTaskUuid(currentUserTask == null ? null : currentUserTask.getUuid()); vo.setOrderId(order.getId()); vo.setOrderUuid(order.getUuid()); vo.setOrderNo(order.getOrderNo()); vo.setTaskStatus(currentUserTask == null ? null : currentUserTask.getTaskStatus()); vo.setNodeCode(currentUserTask == null ? null : currentUserTask.getNodeCode()); vo.setNodeName(resolveNodeName(currentUserTask == null ? null : currentUserTask.getNodeCode())); fillMode(vo, uid, order, currentUserTask); vo.setVisitedUserId(order.getVisitedUserId()); vo.setVisitedUserName(order.getVisitedUserName()); vo.setVisitedDeptName(loadDeptName(order.getVisitedDeptId())); vo.setAccessAreaText(joinNames(parseStringList(order.getPassAccessAreaNames()))); vo.setVisitTypeText(buildVisitTypeText(order)); vo.setVisitReasonText(buildVisitReasonText(order)); vo.setVisitTimeText(buildVisitTimeText(order)); vo.setPassAccessAreaIds(parseLongList(order.getPassAccessAreaIds())); vo.setPassAccessAreaNames(parseStringList(order.getPassAccessAreaNames())); vo.setInvolveOtherLocalUnit(Boolean.TRUE.equals(order.getInvolveOtherLocalUnit())); vo.setLocalUnits(loadLocalUnits(parseLongList(order.getPassLocalUnitIds()))); vo.setVisitors(loadVisitors(order.getId())); List flowTree = approvalFlowViewApplicationService.treeByOrderId(order.getId()); vo.setFlowTree(flowTree == null ? List.of() : flowTree); return vo; } private void fillMode( MiniApprovalDetailVo vo, long uid, AppointmentOrder order, ApprovalTask currentUserTask) { boolean canApprove = currentUserTask != null && ApprovalTaskStatus.PENDING.equals(currentUserTask.getTaskStatus()) && Objects.equals(currentUserTask.getAssigneeUserId(), uid); vo.setCanApprove(canApprove); if (canApprove && Objects.equals(order.getVisitedUserId(), uid) && ApprovalNodeCode.LEVEL1_HANDLER.equals(currentUserTask.getNodeCode())) { vo.setPageMode(PAGE_MODE_HANDLER_PENDING); vo.setApprovalStatusText("待审批"); return; } if (canApprove) { vo.setPageMode(PAGE_MODE_GENERAL_PENDING); vo.setApprovalStatusText("待审批"); return; } vo.setPageMode(PAGE_MODE_GENERAL_READONLY); if (currentUserTask == null) { vo.setApprovalStatusText("已查看"); return; } if (ApprovalTaskStatus.APPROVED.equals(currentUserTask.getTaskStatus())) { vo.setApprovalStatusText("已同意"); return; } if (ApprovalTaskStatus.REJECTED.equals(currentUserTask.getTaskStatus())) { vo.setApprovalStatusText("已拒绝"); return; } vo.setApprovalStatusText("待审批"); } private ApprovalInstance loadInstance(ApprovalTask task, Long orderId) { if (task != null) { ApprovalInstance instance = approvalInstanceMapper.selectById(task.getInstanceId()); if (instance == null || (instance.getDelFlag() != null && instance.getDelFlag() == 1)) { throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "审批实例不存在"); } return instance; } if (orderId == null || orderId <= 0) { throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在"); } List rows = approvalInstanceMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalInstance::getDelFlag, 0) .eq(ApprovalInstance::getOrderId, orderId) .orderByDesc(ApprovalInstance::getId) .last("limit 1")); if (rows.isEmpty()) { throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "审批实例不存在"); } return rows.get(0); } private AppointmentOrder loadOrder(ApprovalInstance instance, Long orderId, String orderUuid) { Long targetOrderId = instance != null ? instance.getOrderId() : orderId; AppointmentOrder order = appointmentOrderMapper.selectById(targetOrderId); if (order == null || (order.getDelFlag() != null && order.getDelFlag() == 1)) { throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在"); } if (orderId != null && orderId > 0) { UuidAssert.match(order, orderUuid); } return order; } private ApprovalTask resolveCurrentUserTask( ApprovalTask task, long uid, ApprovalInstance instance, AppointmentOrder order) { if (task != null) { if (!Objects.equals(task.getAssigneeUserId(), uid)) { throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "无权查看该审批任务"); } return task; } List rows = approvalTaskMapper.selectList( new LambdaQueryWrapper() .eq(ApprovalTask::getDelFlag, 0) .eq(ApprovalTask::getInstanceId, instance.getId()) .eq(ApprovalTask::getAssigneeUserId, uid) .orderByDesc(ApprovalTask::getUpdateTime) .orderByDesc(ApprovalTask::getId)); return rows.isEmpty() ? null : rows.get(0); } private List loadVisitors(Long orderId) { List rows = appointmentVisitorMapper.selectList( new LambdaQueryWrapper() .eq(AppointmentVisitor::getDelFlag, 0) .eq(AppointmentVisitor::getOrderId, orderId) .orderByAsc(AppointmentVisitor::getId)); if (rows.isEmpty()) { return List.of(); } List visitorIds = rows.stream() .map(AppointmentVisitor::getMpVisitorId) .filter(Objects::nonNull) .toList(); Map visitorMap = loadVisitorMap(visitorIds); Map plateMap = loadVisitorPlateMap(orderId); List result = new ArrayList<>(); for (AppointmentVisitor row : rows) { MpVisitor visitor = visitorMap.get(row.getMpVisitorId()); MiniApprovalDetailVisitorVo vo = new MiniApprovalDetailVisitorVo(); vo.setVisitorId(row.getMpVisitorId()); vo.setName(firstNonBlank(row.getName(), visitor == null ? null : visitor.getVisitorName(), "-")); vo.setGender(visitor == null ? null : visitor.getGender()); vo.setCompanyName(visitor == null ? null : visitor.getVisitorCompany()); vo.setMobileMasked( 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( StringUtils.hasText(idCardPlain) ? SensitiveDataUtil.maskIdCard(idCardPlain) : null); vo.setPlateNo(plateMap.get(row.getMpVisitorId())); result.add(vo); } return result; } private Map loadVisitorMap(List visitorIds) { if (visitorIds.isEmpty()) { return Map.of(); } List rows = mpVisitorMapper.selectList( new LambdaQueryWrapper() .eq(MpVisitor::getDelFlag, 0) .in(MpVisitor::getId, visitorIds)); return rows.stream().collect(Collectors.toMap(MpVisitor::getId, x -> x, (a, b) -> a)); } 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) { continue; } plates.computeIfAbsent(row.getDriverVisitorId(), k -> new ArrayList<>()); if (StringUtils.hasText(row.getPlateNo())) { plates.get(row.getDriverVisitorId()).add(row.getPlateNo().trim()); } } Map out = new LinkedHashMap<>(); for (Map.Entry> entry : plates.entrySet()) { out.put(entry.getKey(), String.join("、", entry.getValue())); } return out; } private List loadLocalUnits(List unitIds) { if (unitIds.isEmpty()) { return List.of(); } List rows = mpPassLocalUnitMapper.selectList( new LambdaQueryWrapper() .eq(MpPassLocalUnit::getDelFlag, 0) .in(MpPassLocalUnit::getId, unitIds)); Map nameMap = rows.stream() .collect(Collectors.toMap(MpPassLocalUnit::getId, MpPassLocalUnit::getUnitName, (a, b) -> a)); List result = new ArrayList<>(); for (Long unitId : unitIds) { String name = nameMap.get(unitId); if (!StringUtils.hasText(name)) { continue; } MiniLocalUnitVo vo = new MiniLocalUnitVo(); vo.setId(unitId); vo.setUnitName(name); result.add(vo); } return result; } 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 order) { String code = trimToNull(order.getVisitReasonCode()); if (AppointmentBizConstants.VISIT_REASON_OTHER.equalsIgnoreCase(code)) { String other = trimToNull(order.getVisitReasonOther()); return StringUtils.hasText(other) ? "其他-" + other : "其他"; } if (!StringUtils.hasText(code)) { return "-"; } List rows = bizVisitReasonMapper.selectList( new LambdaQueryWrapper() .eq(BizVisitReason::getDelFlag, 0) .eq(BizVisitReason::getReasonCode, code) .last("limit 1")); return rows.isEmpty() || !StringUtils.hasText(rows.get(0).getReasonName()) ? code : rows.get(0).getReasonName(); } private String buildVisitTimeText(AppointmentOrder order) { if (AppointmentBizConstants.TIME_MODE_SINGLE.equals(order.getVisitTimeMode())) { String slotLabel = switch (trimToNull(order.getVisitTimeSlot())) { case AppointmentBizConstants.TIME_SLOT_ALL_DAY -> "全天"; case AppointmentBizConstants.TIME_SLOT_AM -> "上午"; case AppointmentBizConstants.TIME_SLOT_PM -> "下午"; default -> ""; }; if (order.getVisitStartDate() == null) { return slotLabel; } return StringUtils.hasText(slotLabel) ? order.getVisitStartDate() + " " + slotLabel : order.getVisitStartDate().toString(); } if (order.getVisitStartDate() == null && order.getVisitEndDate() == null) { return "-"; } if (Objects.equals(order.getVisitStartDate(), order.getVisitEndDate()) || order.getVisitEndDate() == null) { return String.valueOf(order.getVisitStartDate()); } return order.getVisitStartDate() + " 至 " + order.getVisitEndDate(); } private String loadDeptName(Long deptId) { if (deptId == null || deptId <= 0) { return null; } SysDept dept = sysDeptMapper.selectById(deptId); return dept == null ? null : dept.getDeptName(); } private String resolveNodeName(String nodeCode) { if (!StringUtils.hasText(nodeCode)) { return null; } return switch (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 -> nodeCode; }; } private List parseLongList(String json) { if (!StringUtils.hasText(json)) { return List.of(); } try { List rows = objectMapper.readValue(json, LONG_LIST_TYPE); if (rows == null) { return List.of(); } return rows.stream() .filter(Objects::nonNull) .filter(id -> id > 0) .collect(Collectors.toCollection(ArrayList::new)); } catch (Exception ex) { return List.of(); } } private List parseStringList(String json) { if (!StringUtils.hasText(json)) { return List.of(); } try { List rows = objectMapper.readValue(json, STRING_LIST_TYPE); if (rows == null) { return List.of(); } return rows.stream().filter(StringUtils::hasText).map(String::trim).toList(); } catch (Exception ex) { return List.of(); } } private String joinNames(List names) { if (names == null || names.isEmpty()) { return null; } return names.stream() .filter(StringUtils::hasText) .map(String::trim) .collect(Collectors.joining("、")); } private String firstNonBlank(String... values) { if (values == null) { return null; } for (String value : values) { if (StringUtils.hasText(value)) { return value.trim(); } } return null; } private String trimToNull(String value) { if (!StringUtils.hasText(value)) { return null; } return value.trim(); } }