liujie
2026-08-04 e9e42b621afdf6ac94f5327ee843b32b10a401b4
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
package com.tourguide.modules.order.service;
 
import com.tourguide.modules.operation.entity.OperationScenicSpot;
import com.tourguide.modules.operation.entity.ScenicPricing;
import com.tourguide.modules.operation.mapper.OperationScenicSpotMapper;
import com.tourguide.modules.operation.mapper.ScenicPricingMapper;
import com.tourguide.modules.order.domain.OrderStatus;
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.user.entity.GuideReceivingSession;
import com.tourguide.modules.user.entity.GuideUser;
import com.tourguide.modules.user.mapper.GuideRealtimeStatusMapper;
import com.tourguide.modules.user.mapper.GuideReceivingSessionMapper;
import com.tourguide.modules.user.mapper.GuideUserMapper;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
 
import java.time.LocalDateTime;
import java.util.List;
 
@Service
public class GuideOrderMatchingService {
    private final TourOrderMapper orderMapper;
    private final GuideReceivingSessionMapper sessionMapper;
    private final GuideUserMapper guideUserMapper;
    private final GuideRealtimeStatusMapper realtimeStatusMapper;
    private final OperationScenicSpotMapper scenicMapper;
    private final ScenicPricingMapper pricingMapper;
    private final GuideOrderEarningSnapshotService earningSnapshotService;
    private final GuideNotificationService notificationService;
 
    public GuideOrderMatchingService(TourOrderMapper orderMapper, GuideReceivingSessionMapper sessionMapper,
                                     GuideUserMapper guideUserMapper, GuideRealtimeStatusMapper realtimeStatusMapper,
                                     OperationScenicSpotMapper scenicMapper, ScenicPricingMapper pricingMapper,
                                     GuideOrderEarningSnapshotService earningSnapshotService,
                                     GuideNotificationService notificationService) {
        this.orderMapper = orderMapper;
        this.sessionMapper = sessionMapper;
        this.guideUserMapper = guideUserMapper;
        this.realtimeStatusMapper = realtimeStatusMapper;
        this.scenicMapper = scenicMapper;
        this.pricingMapper = pricingMapper;
        this.earningSnapshotService = earningSnapshotService;
        this.notificationService = notificationService;
    }
 
    @Transactional
    public boolean match(Long orderId) {
        TourOrder order = orderMapper.selectForUpdate(orderId);
        if (order == null || !OrderStatus.PENDING_ACCEPT.matches(order.getStatus()) || order.getGuideId() != null) return false;
        if (order.getScenicSpotId() == null || order.getMeetingPointId() == null || order.getServicePackageId() == null) return false;
        String mode = order.getOrderType().startsWith("GROUP_") ? "GROUP" : "PRIVATE";
        List<GuideReceivingSession> candidates = sessionMapper.selectMatchCandidates(order.getScenicSpotId(),
                order.getMeetingPointId(), mode, order.getOrderType());
        ScenicPricing pricing = pricingMapper.selectById(order.getServicePackageId());
        OperationScenicSpot scenic = scenicMapper.selectById(order.getScenicSpotId());
        if (pricing == null || scenic == null) return false;
        for (GuideReceivingSession session : candidates) {
            LocalDateTime now = LocalDateTime.now();
            boolean reserved = "GROUP".equals(mode)
                    ? reserveGroup(session, order, pricing, scenic, now)
                    : sessionMapper.assignPrivate(session.getId(), order.getPeopleCount(), now) == 1;
            if (!reserved) continue;
            GuideUser guide = guideUserMapper.selectById(session.getGuideId());
            if (guide == null) continue;
            order.setReceivingSessionId(session.getId());
            order.setGuideId(guide.getId());
            order.setGuideName(guide.getRealName());
            order.setGuideMobile(guide.getMobile());
            order.setAcceptedAt(now);
            order.setStatus(OrderStatus.PENDING_MEETING.getCode());
            earningSnapshotService.apply(order, guide);
            orderMapper.updateById(order);
            guideUserMapper.incrementAcceptedOrderCount(guide.getId());
            if ("PRIVATE".equals(mode)) realtimeStatusMapper.disableReceiving(guide.getId());
            notificationService.notifyOrderAssigned(order);
            return true;
        }
        return false;
    }
 
    private boolean reserveGroup(GuideReceivingSession session, TourOrder order, ScenicPricing pricing,
                                 OperationScenicSpot scenic, LocalDateTime now) {
        Integer maxPeople = pricing.getMaxParticipants();
        if (maxPeople == null || maxPeople < 1) return false;
        int leadMinutes = positive(scenic.getFirstOrderLeadMinutes(), 15);
        int cutoffMinutes = positive(scenic.getVisitorNoOrderMinutes(), leadMinutes);
        return sessionMapper.reserveGroupCapacity(session.getId(), order.getPeopleCount(), maxPeople, now,
                now.plusMinutes(leadMinutes), now.plusMinutes(cutoffMinutes)) == 1;
    }
 
    private int positive(Integer value, int fallback) { return value == null || value < 1 ? fallback : value; }
}