liujie
2026-07-31 0cf54692efc067adca7fa4308af241f723930b58
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
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.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) {
        TourOrder order = requireOrder(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, "当前订单状态不支持退款");
        }
        order.setRefundStatus(RefundStatus.REFUNDED.getCode());
        order.setStatus(OrderStatus.REFUNDED.getCode());
        order.setRefundAmount(order.getPaymentAmount() == null ? order.getAmount() : order.getPaymentAmount());
        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 String generateRefundOrderNo() {
        String time = LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss"));
        return "RF" + time + UUID.randomUUID().toString().replace("-", "").substring(0, 6).toUpperCase();
    }
}