package com.sinata.appointment.service;
|
|
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.JsonProcessingException;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.sinata.appointment.config.MiniStpUtil;
|
import com.sinata.appointment.constants.ApprovalInstanceStatus;
|
import com.sinata.appointment.constants.ApprovalNodeCode;
|
import com.sinata.appointment.constants.ApprovalTaskStatus;
|
import com.sinata.appointment.constants.BizErrorCode;
|
import com.sinata.appointment.dto.MiniApprovalTodoVo;
|
import com.sinata.appointment.dto.MiniApprovalActionRequest;
|
import com.sinata.appointment.dto.MiniHandlerApprovalActionRequest;
|
import com.sinata.appointment.dto.MiniHandlerApprovalTodoVo;
|
import com.sinata.appointment.entity.AppointmentOrder;
|
import com.sinata.appointment.entity.ApprovalInstance;
|
import com.sinata.appointment.entity.ApprovalTask;
|
import com.sinata.appointment.entity.BizApprovalNodeConfig;
|
import com.sinata.appointment.entity.MpPassAccessArea;
|
import com.sinata.appointment.entity.MpPassLocalUnit;
|
import com.sinata.appointment.exception.BizException;
|
import com.sinata.appointment.mapper.AppointmentOrderMapper;
|
import com.sinata.appointment.mapper.ApprovalInstanceMapper;
|
import com.sinata.appointment.mapper.ApprovalTaskMapper;
|
import com.sinata.appointment.mapper.BizApprovalNodeConfigMapper;
|
import com.sinata.appointment.mapper.MpPassAccessAreaMapper;
|
import com.sinata.appointment.mapper.MpPassLocalUnitMapper;
|
import java.time.LocalDate;
|
import java.util.ArrayList;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.stream.Stream;
|
import java.util.stream.Collectors;
|
import lombok.extern.slf4j.Slf4j;
|
import lombok.RequiredArgsConstructor;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
import com.sinata.appointment.util.UuidAssert;
|
|
@Slf4j
|
@Service
|
@RequiredArgsConstructor
|
public class MiniHandlerApprovalApplicationService {
|
|
private final ApprovalTaskMapper approvalTaskMapper;
|
private final ApprovalInstanceMapper approvalInstanceMapper;
|
private final AppointmentOrderMapper appointmentOrderMapper;
|
private final ApprovalEngineService approvalEngineService;
|
private final IntegrationWechatService integrationWechatService;
|
private final PostApprovalOrchestrationService postApprovalOrchestrationService;
|
private final BizApprovalNodeConfigMapper bizApprovalNodeConfigMapper;
|
private final MpPassAccessAreaMapper mpPassAccessAreaMapper;
|
private final MpPassLocalUnitMapper mpPassLocalUnitMapper;
|
private final ObjectMapper objectMapper;
|
|
public IPage<MiniApprovalTodoVo> pageMyTodoAll(long page, long size) {
|
return pageMyTodoAll(page, size, null, null);
|
}
|
|
public IPage<MiniApprovalTodoVo> pageMyTodoAll(
|
long page, long size, String visitedUserNameKeyword, LocalDate visitDate) {
|
long uid = MiniStpUtil.getLoginIdAsLong();
|
List<ApprovalTask> tasks =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.eq(ApprovalTask::getAssigneeUserId, uid)
|
.eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)
|
.orderByDesc(ApprovalTask::getCreateTime)
|
.orderByDesc(ApprovalTask::getId));
|
if (tasks.isEmpty()) {
|
return new Page<>(page, size);
|
}
|
List<Long> instanceIds =
|
tasks.stream()
|
.map(ApprovalTask::getInstanceId)
|
.filter(Objects::nonNull)
|
.toList();
|
Map<Long, ApprovalInstance> instanceMap = loadInstanceMap(instanceIds);
|
List<Long> orderIds =
|
instanceMap.values().stream()
|
.map(ApprovalInstance::getOrderId)
|
.filter(Objects::nonNull)
|
.toList();
|
Map<Long, AppointmentOrder> orderMap = loadOrderMap(orderIds);
|
Map<String, String> nodeNameMap = loadNodeNameMap();
|
List<MiniApprovalTodoVo> rowsAll =
|
tasks.stream()
|
.filter(t -> matchTodoFilters(t, instanceMap, orderMap, visitedUserNameKeyword, visitDate))
|
.map(t -> toMiniTodoVo(t, instanceMap, orderMap, nodeNameMap))
|
.toList();
|
return slicePage(page, size, rowsAll);
|
}
|
|
public IPage<MiniHandlerApprovalTodoVo> pageMyTodo(long page, long size) {
|
return pageMyTodo(page, size, null, null);
|
}
|
|
public IPage<MiniHandlerApprovalTodoVo> pageMyTodo(
|
long page, long size, String visitedUserNameKeyword, LocalDate visitDate) {
|
long uid = MiniStpUtil.getLoginIdAsLong();
|
List<ApprovalTask> tasks =
|
approvalTaskMapper.selectList(
|
new LambdaQueryWrapper<ApprovalTask>()
|
.eq(ApprovalTask::getAssigneeUserId, uid)
|
.eq(ApprovalTask::getNodeCode, ApprovalNodeCode.LEVEL1_HANDLER)
|
.eq(ApprovalTask::getTaskStatus, ApprovalTaskStatus.PENDING)
|
.orderByDesc(ApprovalTask::getCreateTime)
|
.orderByDesc(ApprovalTask::getId));
|
if (tasks.isEmpty()) {
|
return new Page<>(page, size);
|
}
|
List<Long> instanceIds =
|
tasks.stream()
|
.map(ApprovalTask::getInstanceId)
|
.filter(Objects::nonNull)
|
.toList();
|
Map<Long, ApprovalInstance> instanceMap = loadInstanceMap(instanceIds);
|
List<Long> orderIds =
|
instanceMap.values().stream()
|
.map(ApprovalInstance::getOrderId)
|
.filter(Objects::nonNull)
|
.toList();
|
Map<Long, AppointmentOrder> orderMap = loadOrderMap(orderIds);
|
List<MiniHandlerApprovalTodoVo> rowsAll =
|
tasks.stream()
|
.filter(t -> matchTodoFilters(t, instanceMap, orderMap, visitedUserNameKeyword, visitDate))
|
.map(t -> toTodoVo(t, instanceMap, orderMap))
|
.toList();
|
return slicePage(page, size, rowsAll);
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void handleAction(MiniHandlerApprovalActionRequest req) {
|
long uid = MiniStpUtil.getLoginIdAsLong();
|
ApprovalTask task = approvalTaskMapper.selectById(req.getTaskId());
|
if (task == null || (task.getDelFlag() != null && task.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.APPROVAL_TASK_NOT_FOUND, "审批任务不存在");
|
}
|
UuidAssert.match(task, req.getTaskUuid());
|
if (!ApprovalNodeCode.LEVEL1_HANDLER.equals(task.getNodeCode())) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "仅支持经办人一级审批");
|
}
|
if (!ApprovalTaskStatus.PENDING.equals(task.getTaskStatus())) {
|
throw new BizException(BizErrorCode.APPROVAL_CONFLICT, "任务已处理");
|
}
|
if (!Objects.equals(task.getAssigneeUserId(), uid)) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "无权处理该任务");
|
}
|
String comment = req.getComment().trim();
|
if (!StringUtils.hasText(comment)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "审批意见不能为空");
|
}
|
|
ApprovalInstance inst = approvalInstanceMapper.selectById(task.getInstanceId());
|
if (inst == null || (inst.getDelFlag() != null && inst.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "审批实例不存在");
|
}
|
AppointmentOrder order = appointmentOrderMapper.selectById(inst.getOrderId());
|
if (order == null || (order.getDelFlag() != null && order.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在");
|
}
|
|
if (Boolean.FALSE.equals(req.getApprove())) {
|
task.setTaskStatus(ApprovalTaskStatus.REJECTED);
|
task.setComment(comment);
|
approvalTaskMapper.updateById(task);
|
approvalEngineService.rejectInstance(inst, comment);
|
postApprovalOrchestrationService.onOrderRejected(order, comment);
|
return;
|
}
|
|
fillOrderApprovalExtraFields(order, req);
|
appointmentOrderMapper.updateById(order);
|
|
task.setTaskStatus(ApprovalTaskStatus.APPROVED);
|
task.setComment(comment);
|
approvalTaskMapper.updateById(task);
|
approvalEngineService.markGroupSkippedExcept(task);
|
approvalEngineService.rebuildAfterHandlerApproved(task, inst, order);
|
ApprovalInstance latest = approvalInstanceMapper.selectById(inst.getId());
|
if (ApprovalInstanceStatus.APPROVED.equals(latest.getStatus())) {
|
AppointmentOrder fresh = appointmentOrderMapper.selectById(order.getId());
|
postApprovalOrchestrationService.onOrderFullyApproved(fresh);
|
return;
|
}
|
if (ApprovalInstanceStatus.IN_PROGRESS.equals(latest.getStatus())) {
|
try {
|
integrationWechatService.notifyCurrentApprovers(order, latest.getId());
|
} catch (Exception ex) {
|
log.warn("notify current approvers failed, orderId={}", order.getId(), ex);
|
}
|
}
|
}
|
|
@Transactional(rollbackFor = Exception.class)
|
public void handleOtherAction(MiniApprovalActionRequest req) {
|
long uid = MiniStpUtil.getLoginIdAsLong();
|
ApprovalTask task = approvalTaskMapper.selectById(req.getTaskId());
|
if (task == null || (task.getDelFlag() != null && task.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.APPROVAL_TASK_NOT_FOUND, "审批任务不存在");
|
}
|
UuidAssert.match(task, req.getTaskUuid());
|
if (ApprovalNodeCode.LEVEL1_HANDLER.equals(task.getNodeCode())) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "经办人一级请走专用审批接口");
|
}
|
if (!ApprovalTaskStatus.PENDING.equals(task.getTaskStatus())) {
|
throw new BizException(BizErrorCode.APPROVAL_CONFLICT, "任务已处理");
|
}
|
if (!Objects.equals(task.getAssigneeUserId(), uid)) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "无权处理该任务");
|
}
|
|
ApprovalInstance inst = approvalInstanceMapper.selectById(task.getInstanceId());
|
if (inst == null || (inst.getDelFlag() != null && inst.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "审批实例不存在");
|
}
|
AppointmentOrder order = appointmentOrderMapper.selectById(inst.getOrderId());
|
if (order == null || (order.getDelFlag() != null && order.getDelFlag() == 1)) {
|
throw new BizException(BizErrorCode.ORDER_NOT_FOUND, "预约单不存在");
|
}
|
|
String comment = req.getComment() == null ? null : req.getComment().trim();
|
appendNoticeIdsIfNeeded(task, req.getNoticeIds());
|
if (Boolean.FALSE.equals(req.getApprove())) {
|
String reason = StringUtils.hasText(comment) ? comment : "拒绝";
|
task.setTaskStatus(ApprovalTaskStatus.REJECTED);
|
task.setComment(reason);
|
approvalTaskMapper.updateById(task);
|
approvalEngineService.rejectInstance(inst, reason);
|
postApprovalOrchestrationService.onOrderRejected(order, reason);
|
return;
|
}
|
|
task.setTaskStatus(ApprovalTaskStatus.APPROVED);
|
task.setComment(comment);
|
approvalTaskMapper.updateById(task);
|
approvalEngineService.markGroupSkippedExcept(task);
|
if (approvalEngineService.isCurrentNodeComplete(inst)) {
|
approvalEngineService.advanceOrComplete(inst, order);
|
ApprovalInstance latest = approvalInstanceMapper.selectById(inst.getId());
|
if (ApprovalInstanceStatus.APPROVED.equals(latest.getStatus())) {
|
AppointmentOrder fresh = appointmentOrderMapper.selectById(order.getId());
|
postApprovalOrchestrationService.onOrderFullyApproved(fresh);
|
return;
|
}
|
if (ApprovalInstanceStatus.IN_PROGRESS.equals(latest.getStatus())) {
|
try {
|
integrationWechatService.notifyCurrentApprovers(order, latest.getId());
|
} catch (Exception ex) {
|
log.warn("notify current approvers failed, orderId={}", order.getId(), ex);
|
}
|
}
|
}
|
}
|
|
private void appendNoticeIdsIfNeeded(ApprovalTask task, List<Long> noticeIds) {
|
if (!ApprovalNodeCode.LEVEL3_LOCAL_DEPT_LEADER.equals(task.getNodeCode())) {
|
return;
|
}
|
List<Long> normalized = normalizeIds(noticeIds);
|
if (normalized.isEmpty()) {
|
return;
|
}
|
LinkedHashSet<Long> merged = new LinkedHashSet<>(parseNoticeIds(task.getNoticeIds()));
|
merged.addAll(normalized);
|
task.setNoticeIds(merged.stream().map(String::valueOf).collect(Collectors.joining(",")));
|
}
|
|
private List<Long> parseNoticeIds(String raw) {
|
if (!StringUtils.hasText(raw)) {
|
return List.of();
|
}
|
return Stream.of(raw.split(","))
|
.filter(StringUtils::hasText)
|
.map(String::trim)
|
.map(value -> {
|
try {
|
return Long.parseLong(value);
|
} catch (NumberFormatException ex) {
|
return null;
|
}
|
})
|
.filter(Objects::nonNull)
|
.filter(id -> id > 0)
|
.distinct()
|
.toList();
|
}
|
|
private Map<Long, AppointmentOrder> loadOrderMap(List<Long> orderIds) {
|
if (orderIds.isEmpty()) {
|
return Map.of();
|
}
|
List<AppointmentOrder> rows =
|
appointmentOrderMapper.selectList(
|
new LambdaQueryWrapper<AppointmentOrder>()
|
.eq(AppointmentOrder::getDelFlag, 0)
|
.in(AppointmentOrder::getId, orderIds));
|
return rows.stream().collect(Collectors.toMap(AppointmentOrder::getId, x -> x, (a, b) -> a));
|
}
|
|
private Map<Long, ApprovalInstance> loadInstanceMap(List<Long> instanceIds) {
|
if (instanceIds.isEmpty()) {
|
return Map.of();
|
}
|
List<ApprovalInstance> rows =
|
approvalInstanceMapper.selectList(
|
new LambdaQueryWrapper<ApprovalInstance>()
|
.eq(ApprovalInstance::getDelFlag, 0)
|
.in(ApprovalInstance::getId, instanceIds));
|
return rows.stream().collect(Collectors.toMap(ApprovalInstance::getId, x -> x, (a, b) -> a));
|
}
|
|
private MiniHandlerApprovalTodoVo toTodoVo(
|
ApprovalTask task,
|
Map<Long, ApprovalInstance> instanceMap,
|
Map<Long, AppointmentOrder> orderMap) {
|
MiniHandlerApprovalTodoVo vo = new MiniHandlerApprovalTodoVo();
|
vo.setTaskId(task.getId());
|
vo.setTaskUuid(task.getUuid());
|
vo.setCreateTime(task.getCreateTime());
|
ApprovalInstance inst = instanceMap.get(task.getInstanceId());
|
if (inst == null) {
|
return vo;
|
}
|
AppointmentOrder order = orderMap.get(inst.getOrderId());
|
if (order == null) {
|
return vo;
|
}
|
vo.setOrderId(order.getId());
|
vo.setOrderUuid(order.getUuid());
|
vo.setOrderNo(order.getOrderNo());
|
vo.setVisitedUserName(order.getVisitedUserName());
|
vo.setVisitStartDate(order.getVisitStartDate());
|
vo.setVisitEndDate(order.getVisitEndDate());
|
vo.setVisitReasonCode(order.getVisitReasonCode());
|
return vo;
|
}
|
|
private Map<String, String> loadNodeNameMap() {
|
List<BizApprovalNodeConfig> rows =
|
bizApprovalNodeConfigMapper.selectList(
|
new LambdaQueryWrapper<BizApprovalNodeConfig>()
|
.eq(BizApprovalNodeConfig::getDelFlag, 0)
|
.orderByAsc(BizApprovalNodeConfig::getSortOrder));
|
Map<String, String> out = new java.util.LinkedHashMap<>();
|
for (BizApprovalNodeConfig row : rows) {
|
out.put(row.getNodeCode(), row.getNodeName());
|
}
|
return out;
|
}
|
|
private MiniApprovalTodoVo toMiniTodoVo(
|
ApprovalTask task,
|
Map<Long, ApprovalInstance> instanceMap,
|
Map<Long, AppointmentOrder> orderMap,
|
Map<String, String> nodeNameMap) {
|
MiniApprovalTodoVo vo = new MiniApprovalTodoVo();
|
vo.setTaskId(task.getId());
|
vo.setTaskUuid(task.getUuid());
|
vo.setCreateTime(task.getCreateTime());
|
vo.setNodeCode(task.getNodeCode());
|
vo.setNodeName(nodeNameMap.getOrDefault(task.getNodeCode(), task.getNodeCode()));
|
ApprovalInstance inst = instanceMap.get(task.getInstanceId());
|
if (inst == null) {
|
return vo;
|
}
|
AppointmentOrder order = orderMap.get(inst.getOrderId());
|
if (order == null) {
|
return vo;
|
}
|
vo.setOrderId(order.getId());
|
vo.setOrderUuid(order.getUuid());
|
vo.setOrderNo(order.getOrderNo());
|
vo.setVisitedUserName(order.getVisitedUserName());
|
vo.setVisitStartDate(order.getVisitStartDate());
|
vo.setVisitEndDate(order.getVisitEndDate());
|
vo.setVisitReasonCode(order.getVisitReasonCode());
|
return vo;
|
}
|
|
private void fillOrderApprovalExtraFields(AppointmentOrder order, MiniHandlerApprovalActionRequest req) {
|
List<Long> areaIds = normalizeIds(req.getPassAccessAreaIds());
|
if (areaIds.isEmpty()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "通行区域不能为空");
|
}
|
List<MpPassAccessArea> areas =
|
mpPassAccessAreaMapper.selectList(
|
new LambdaQueryWrapper<MpPassAccessArea>()
|
.eq(MpPassAccessArea::getDelFlag, 0)
|
.in(MpPassAccessArea::getId, areaIds));
|
Map<Long, String> areaNameMap =
|
areas.stream()
|
.collect(Collectors.toMap(MpPassAccessArea::getId, MpPassAccessArea::getAreaName, (a, b) -> a));
|
List<String> areaNames = new ArrayList<>();
|
for (Long id : areaIds) {
|
String name = areaNameMap.get(id);
|
if (!StringUtils.hasText(name)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "通行区域不存在: " + id);
|
}
|
areaNames.add(name);
|
}
|
|
boolean involve = Boolean.TRUE.equals(req.getInvolveOtherLocalUnit());
|
List<Long> unitIds = normalizeIds(req.getPassLocalUnitIds());
|
List<String> unitNames = List.of();
|
if (involve) {
|
if (unitIds.isEmpty()) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "涉及其他属地单位时,单位列表不能为空");
|
}
|
List<MpPassLocalUnit> units =
|
mpPassLocalUnitMapper.selectList(
|
new LambdaQueryWrapper<MpPassLocalUnit>()
|
.eq(MpPassLocalUnit::getDelFlag, 0)
|
.in(MpPassLocalUnit::getId, unitIds));
|
Map<Long, String> unitNameMap =
|
units.stream()
|
.collect(Collectors.toMap(MpPassLocalUnit::getId, MpPassLocalUnit::getUnitName, (a, b) -> a));
|
List<String> names = new ArrayList<>();
|
for (Long id : unitIds) {
|
String name = unitNameMap.get(id);
|
if (!StringUtils.hasText(name)) {
|
throw new BizException(BizErrorCode.AUTH_INVALID_CREDENTIALS, "属地单位不存在: " + id);
|
}
|
names.add(name);
|
}
|
unitNames = names;
|
} else {
|
unitIds = List.of();
|
}
|
|
order.setPassAccessAreaIds(writeJson(areaIds));
|
order.setPassAccessAreaNames(writeJson(areaNames));
|
order.setInvolveOtherLocalUnit(involve);
|
order.setPassLocalUnitIds(writeJson(unitIds));
|
order.setPassLocalUnitNames(writeJson(unitNames));
|
}
|
|
private static List<Long> normalizeIds(List<Long> ids) {
|
if (ids == null || ids.isEmpty()) {
|
return List.of();
|
}
|
LinkedHashSet<Long> out = new LinkedHashSet<>();
|
for (Long id : ids) {
|
if (id != null && id > 0) {
|
out.add(id);
|
}
|
}
|
return out.stream().toList();
|
}
|
|
private String writeJson(Object value) {
|
try {
|
return objectMapper.writeValueAsString(value);
|
} catch (JsonProcessingException ex) {
|
throw new BizException(BizErrorCode.AUTH_FORBIDDEN, "数据序列化失败");
|
}
|
}
|
|
private boolean matchTodoFilters(
|
ApprovalTask task,
|
Map<Long, ApprovalInstance> instanceMap,
|
Map<Long, AppointmentOrder> orderMap,
|
String visitedUserNameKeyword,
|
LocalDate visitDate) {
|
ApprovalInstance inst = instanceMap.get(task.getInstanceId());
|
if (inst == null) {
|
return false;
|
}
|
AppointmentOrder order = orderMap.get(inst.getOrderId());
|
if (order == null) {
|
return false;
|
}
|
if (StringUtils.hasText(visitedUserNameKeyword)) {
|
String name = order.getVisitedUserName();
|
if (!StringUtils.hasText(name) || !name.contains(visitedUserNameKeyword.trim())) {
|
return false;
|
}
|
}
|
if (visitDate != null) {
|
if (order.getVisitStartDate() == null || order.getVisitEndDate() == null) {
|
return false;
|
}
|
if (visitDate.isBefore(order.getVisitStartDate()) || visitDate.isAfter(order.getVisitEndDate())) {
|
return false;
|
}
|
}
|
return true;
|
}
|
|
private <T> IPage<T> slicePage(long page, long size, List<T> rowsAll) {
|
long total = rowsAll == null ? 0 : rowsAll.size();
|
long current = Math.max(1L, page);
|
long pageSize = Math.max(1L, size);
|
int from = (int) Math.min(total, (current - 1) * pageSize);
|
int to = (int) Math.min(total, from + pageSize);
|
List<T> records = from >= to ? List.of() : rowsAll.subList(from, to);
|
Page<T> out = new Page<>(current, pageSize);
|
out.setTotal(total);
|
out.setRecords(records);
|
return out;
|
}
|
}
|