liujie
2026-09-11 4f6f15b818704079ff2e4e4719b798f48d68a335
backend/src/main/java/com/tourguide/modules/order/service/TourOrderService.java
@@ -5,38 +5,67 @@
import com.tourguide.common.api.PageResponse;
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.order.dto.TourOrderResponse;
import com.tourguide.modules.order.dto.TourOrderRefundResponse;
import com.tourguide.modules.order.domain.OrderStatus;
import com.tourguide.modules.order.domain.RefundAmountCalculator;
import com.tourguide.modules.order.domain.RefundStatus;
import com.tourguide.modules.order.entity.TourOrder;
import com.tourguide.modules.order.entity.TourOrderRefund;
import com.tourguide.modules.order.mapper.TourOrderMapper;
import com.tourguide.modules.order.mapper.TourOrderRefundMapper;
import com.tourguide.modules.system.service.GuideNotificationService;
import com.tourguide.modules.review.entity.OrderReview;
import com.tourguide.modules.review.mapper.OrderReviewMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class TourOrderService {
    private static final Logger log = LoggerFactory.getLogger(TourOrderService.class);
    private final TourOrderMapper orderMapper;
    private final OrderReviewMapper reviewMapper;
    private final GuideNotificationService notificationService;
    private final OrderRefundService orderRefundService;
    private final TourOrderRefundMapper refundMapper;
    private final GuideSessionTransitionService sessionTransitionService;
    public TourOrderService(TourOrderMapper orderMapper, OrderReviewMapper reviewMapper,
                            GuideNotificationService notificationService) {
                            GuideNotificationService notificationService,
                            OrderRefundService orderRefundService) {
        this(orderMapper, reviewMapper, notificationService, orderRefundService, null, null);
    }
    public TourOrderService(TourOrderMapper orderMapper, OrderReviewMapper reviewMapper,
                            GuideNotificationService notificationService,
                            OrderRefundService orderRefundService,
                            TourOrderRefundMapper refundMapper) {
        this(orderMapper, reviewMapper, notificationService, orderRefundService, refundMapper, null);
    }
    @Autowired
    public TourOrderService(TourOrderMapper orderMapper, OrderReviewMapper reviewMapper,
                            GuideNotificationService notificationService,
                            OrderRefundService orderRefundService,
                            TourOrderRefundMapper refundMapper,
                            GuideSessionTransitionService sessionTransitionService) {
        this.orderMapper = orderMapper;
        this.reviewMapper = reviewMapper;
        this.notificationService = notificationService;
        this.orderRefundService = orderRefundService;
        this.refundMapper = refundMapper;
        this.sessionTransitionService = sessionTransitionService;
    }
    public PageResponse<TourOrderResponse> page(long page, long size, String keyword, Long visitorId,
    public PageResponse<TourOrderResponse> page(long page, long size, String keyword, Long visitorId, Long guideId,
                                                String scenicName, String status,
                                                LocalDate startDate, LocalDate endDate) {
        LambdaQueryWrapper<TourOrder> query = new LambdaQueryWrapper<TourOrder>();
@@ -49,6 +78,7 @@
                    .or().like(TourOrder::getGuideMobile, searchKeyword));
        }
        query.eq(visitorId != null, TourOrder::getVisitorId, visitorId)
                .eq(guideId != null, TourOrder::getGuideId, guideId)
                .eq(StringUtils.hasText(scenicName), TourOrder::getScenicName, scenicName)
                .eq(StringUtils.hasText(status), TourOrder::getStatus, status)
                .ge(startDate != null, TourOrder::getCreatedAt, startDate == null ? null : startDate.atStartOfDay())
@@ -64,28 +94,47 @@
        TourOrder order = requireOrder(id);
        OrderReview review = reviewMapper.selectOne(new LambdaQueryWrapper<OrderReview>()
                .eq(OrderReview::getOrderId, id)
                .eq(OrderReview::getStatus, 1)
                .last("LIMIT 1"));
        return TourOrderResponse.from(order, review);
        TourOrderResponse response = TourOrderResponse.from(order, review);
        if (refundMapper == null) return response;
        List<TourOrderRefund> records = refundMapper.selectByOrderId(id);
        if (records == null) return response;
        List<TourOrderRefundResponse> refundRecords = records.stream()
                .map(TourOrderRefundResponse::new)
                .collect(Collectors.toList());
        return response.withRefundRecords(refundRecords);
    }
    public List<String> scenicOptions() {
        return orderMapper.selectScenicOptions();
    }
    @Transactional
    @Transactional(noRollbackFor = BusinessException.class)
    public TourOrderResponse refund(Long id, BigDecimal refundAmount) {
        TourOrder order = requireOrderForUpdate(id);
        boolean notifyGuide = OrderStatus.PENDING_MEETING.matches(order.getStatus()) && order.getGuideId() != null;
        if (OrderStatus.REFUNDED.matches(order.getStatus()) || RefundStatus.REFUNDED.matches(order.getRefundStatus())) {
            throw new BusinessException(409, "订单已退款,请勿重复操作");
        }
        if (OrderStatus.CANCELLED.matches(order.getStatus())) {
        if (order.getPaymentTime() == null) {
            throw new BusinessException(409, "未支付订单不能退款");
        }
        if (RefundStatus.REFUNDING.matches(order.getRefundStatus())) {
            throw new BusinessException(409, "退款正在处理中,请勿重复操作");
        }
        if (OrderStatus.CANCELLED.matches(order.getStatus())
                && !RefundStatus.REFUND_FAILED.matches(order.getRefundStatus())
                && !RefundStatus.PARTIAL.matches(order.getRefundStatus())) {
            throw new BusinessException(409, "已取消订单不能退款");
        }
        if (!OrderStatus.isRefundable(order.getStatus())) {
        boolean followUpRefund = OrderStatus.CANCELLED.matches(order.getStatus())
                && (RefundStatus.REFUND_FAILED.matches(order.getRefundStatus())
                || RefundStatus.PARTIAL.matches(order.getRefundStatus()));
        if (!OrderStatus.isRefundable(order.getStatus()) && !followUpRefund) {
            throw new BusinessException(409, "当前订单状态不支持退款");
        }
        BigDecimal refundableAmount = order.getPaymentAmount() == null ? order.getAmount() : order.getPaymentAmount();
        BigDecimal refundableAmount = RefundAmountCalculator.refundableAmount(order);
        if (refundableAmount == null || refundableAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException(409, "订单无可退金额");
        }
@@ -95,14 +144,20 @@
        if (refundAmount.compareTo(refundableAmount) > 0) {
            throw new BusinessException(400, "退款金额不能超过当前可退金额");
        }
        order.setRefundStatus(RefundStatus.REFUNDED.getCode());
        order.setStatus(OrderStatus.REFUNDED.getCode());
        order.setRefundAmount(refundAmount);
        order.setRefundTime(LocalDateTime.now());
        order.setRefundOrderNo(generateRefundOrderNo());
        order.setRefundMethod(order.getPaymentMethod());
        orderMapper.updateById(order);
        if (notifyGuide) notificationService.notifyOrderCancelled(order);
        if (!orderRefundService.requestRefund(order, refundAmount)) {
            throw new BusinessException(502, "支付渠道退款失败,请稍后重试");
        }
        if (notifyGuide) {
            if (sessionTransitionService != null) {
                sessionTransitionService.restoreAfterOrderCancellation(order);
            }
            try {
                notificationService.notifyOrderCancelled(order);
            } catch (RuntimeException exception) {
                log.warn("Failed to notify guide after refund submission, orderId={}, guideId={}",
                        order.getId(), order.getGuideId(), exception);
            }
        }
        return TourOrderResponse.from(order);
    }
@@ -122,8 +177,4 @@
        return order;
    }
    private String generateRefundOrderNo() {
        String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return "RF" + time + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
    }
}