liujie
2026-08-21 ad5db85e540947e82c7a4d5612879b3b3ecdf459
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
package com.tourguide.modules.order.service;
 
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tourguide.common.api.PageResponse;
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.order.dto.TourOrderResponse;
import com.tourguide.modules.order.domain.OrderStatus;
import com.tourguide.modules.order.domain.RefundStatus;
import com.tourguide.modules.order.entity.TourOrder;
import com.tourguide.modules.order.mapper.TourOrderMapper;
import com.tourguide.modules.system.service.GuideNotificationService;
import com.tourguide.modules.review.entity.OrderReview;
import com.tourguide.modules.review.mapper.OrderReviewMapper;
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 final TourOrderMapper orderMapper;
    private final OrderReviewMapper reviewMapper;
    private final GuideNotificationService notificationService;
 
    public TourOrderService(TourOrderMapper orderMapper, OrderReviewMapper reviewMapper,
                            GuideNotificationService notificationService) {
        this.orderMapper = orderMapper;
        this.reviewMapper = reviewMapper;
        this.notificationService = notificationService;
    }
 
    public PageResponse<TourOrderResponse> page(long page, long size, String keyword, Long visitorId,
                                                String scenicName, String status,
                                                LocalDate startDate, LocalDate endDate) {
        LambdaQueryWrapper<TourOrder> query = new LambdaQueryWrapper<TourOrder>();
        if (StringUtils.hasText(keyword)) {
            String searchKeyword = keyword.trim();
            query.and(wrapper -> wrapper.like(TourOrder::getOrderNo, searchKeyword)
                    .or().like(TourOrder::getVisitorNickname, searchKeyword)
                    .or().like(TourOrder::getVisitorMobile, searchKeyword)
                    .or().like(TourOrder::getGuideName, searchKeyword)
                    .or().like(TourOrder::getGuideMobile, searchKeyword));
        }
        query.eq(visitorId != null, TourOrder::getVisitorId, visitorId)
                .eq(StringUtils.hasText(scenicName), TourOrder::getScenicName, scenicName)
                .eq(StringUtils.hasText(status), TourOrder::getStatus, status)
                .ge(startDate != null, TourOrder::getCreatedAt, startDate == null ? null : startDate.atStartOfDay())
                .lt(endDate != null, TourOrder::getCreatedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay())
                .orderByDesc(TourOrder::getId);
        Page<TourOrder> result = orderMapper.selectPage(new Page<TourOrder>(page, size), query);
        return new PageResponse<TourOrderResponse>(
                result.getRecords().stream().map(TourOrderResponse::from).collect(Collectors.toList()),
                result.getTotal(), result.getCurrent(), result.getSize());
    }
 
    public TourOrderResponse detail(Long id) {
        TourOrder order = requireOrder(id);
        OrderReview review = reviewMapper.selectOne(new LambdaQueryWrapper<OrderReview>()
                .eq(OrderReview::getOrderId, id)
                .last("LIMIT 1"));
        return TourOrderResponse.from(order, review);
    }
 
    public List<String> scenicOptions() {
        return orderMapper.selectScenicOptions();
    }
 
    @Transactional
    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())) {
            throw new BusinessException(409, "已取消订单不能退款");
        }
        if (!OrderStatus.isRefundable(order.getStatus())) {
            throw new BusinessException(409, "当前订单状态不支持退款");
        }
        BigDecimal refundableAmount = order.getPaymentAmount() == null ? order.getAmount() : order.getPaymentAmount();
        if (refundableAmount == null || refundableAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException(409, "订单无可退金额");
        }
        if (refundAmount == null || refundAmount.compareTo(BigDecimal.ZERO) <= 0) {
            throw new BusinessException(400, "退款金额必须大于0");
        }
        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);
        return TourOrderResponse.from(order);
    }
 
    private TourOrder requireOrder(Long id) {
        TourOrder order = orderMapper.selectById(id);
        if (order == null) {
            throw new BusinessException(404, "订单不存在");
        }
        return order;
    }
 
    private TourOrder requireOrderForUpdate(Long id) {
        TourOrder order = orderMapper.selectForUpdate(id);
        if (order == null) {
            throw new BusinessException(404, "订单不存在");
        }
        return order;
    }
 
    private String generateRefundOrderNo() {
        String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return "RF" + time + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
    }
}