package com.tourguide.modules.user.service;
|
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.tourguide.common.exception.BusinessException;
|
import com.tourguide.modules.operation.dto.PlatformSettingsResponse;
|
import com.tourguide.modules.operation.entity.OperationScenicSpot;
|
import com.tourguide.modules.operation.entity.ScenicMeetingPoint;
|
import com.tourguide.modules.operation.entity.ScenicPricing;
|
import com.tourguide.modules.operation.mapper.OperationScenicSpotMapper;
|
import com.tourguide.modules.operation.mapper.ScenicMeetingPointMapper;
|
import com.tourguide.modules.operation.mapper.ScenicPricingMapper;
|
import com.tourguide.modules.operation.service.PlatformSettingsService;
|
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.user.dto.GuideReceivingOptionResponse;
|
import com.tourguide.modules.user.dto.GuideReceivingStartRequest;
|
import com.tourguide.modules.user.entity.GuideRealtimeStatus;
|
import com.tourguide.modules.user.entity.GuideReceivingSession;
|
import com.tourguide.modules.user.entity.GuideScenicConfig;
|
import com.tourguide.modules.user.entity.GuideTeamScenicConfig;
|
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.GuideScenicConfigMapper;
|
import com.tourguide.modules.user.mapper.GuideTeamScenicConfigMapper;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.HashSet;
|
import java.util.List;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
@Service
|
public class GuideReceivingService {
|
private final GuideUserService guideUserService;
|
private final GuideScenicConfigMapper guideScenicMapper;
|
private final GuideTeamScenicConfigMapper teamScenicMapper;
|
private final OperationScenicSpotMapper scenicMapper;
|
private final ScenicMeetingPointMapper meetingPointMapper;
|
private final ScenicPricingMapper pricingMapper;
|
private final GuideReceivingSessionMapper sessionMapper;
|
private final GuideRealtimeStatusMapper realtimeStatusMapper;
|
private final PlatformSettingsService platformSettingsService;
|
private final TourOrderMapper orderMapper;
|
|
public GuideReceivingService(GuideUserService guideUserService, GuideScenicConfigMapper guideScenicMapper,
|
GuideTeamScenicConfigMapper teamScenicMapper, OperationScenicSpotMapper scenicMapper,
|
ScenicMeetingPointMapper meetingPointMapper, ScenicPricingMapper pricingMapper,
|
GuideReceivingSessionMapper sessionMapper, GuideRealtimeStatusMapper realtimeStatusMapper,
|
PlatformSettingsService platformSettingsService, TourOrderMapper orderMapper) {
|
this.guideUserService = guideUserService;
|
this.guideScenicMapper = guideScenicMapper;
|
this.teamScenicMapper = teamScenicMapper;
|
this.scenicMapper = scenicMapper;
|
this.meetingPointMapper = meetingPointMapper;
|
this.pricingMapper = pricingMapper;
|
this.sessionMapper = sessionMapper;
|
this.realtimeStatusMapper = realtimeStatusMapper;
|
this.platformSettingsService = platformSettingsService;
|
this.orderMapper = orderMapper;
|
}
|
|
public GuideReceivingOptionResponse options(Long guideId) {
|
GuideUser guide = guideUserService.findEnabledById(guideId);
|
PlatformSettingsResponse settings = platformSettingsService.getSettings();
|
boolean premiumEnabled = premiumEnabled(guide, settings);
|
Set<String> scenicNames = configuredScenicNames(guide);
|
if (scenicNames.isEmpty()) {
|
return new GuideReceivingOptionResponse(premiumEnabled, value(settings.getPreciseDispatchOrderCount()),
|
settings.getPreciseDispatchRating(), new ArrayList<>());
|
}
|
List<OperationScenicSpot> scenics = scenicMapper.selectList(new LambdaQueryWrapper<OperationScenicSpot>()
|
.eq(OperationScenicSpot::getStatus, 1).in(OperationScenicSpot::getScenicName, scenicNames)
|
.orderByAsc(OperationScenicSpot::getId));
|
List<GuideReceivingOptionResponse.ScenicOption> rows = new ArrayList<>();
|
for (OperationScenicSpot scenic : scenics) {
|
List<GuideReceivingOptionResponse.MeetingPointOption> points = meetingPointMapper.selectList(
|
new LambdaQueryWrapper<ScenicMeetingPoint>().eq(ScenicMeetingPoint::getScenicSpotId, scenic.getId())
|
.eq(ScenicMeetingPoint::getStatus, 1).orderByAsc(ScenicMeetingPoint::getSortOrder))
|
.stream().map(point -> new GuideReceivingOptionResponse.MeetingPointOption(point.getId(),
|
point.getPointName(), point.getLongitude(), point.getLatitude())).collect(Collectors.toList());
|
List<GuideReceivingOptionResponse.ServiceOption> services = pricingMapper.selectList(
|
new LambdaQueryWrapper<ScenicPricing>().eq(ScenicPricing::getScenicSpotId, scenic.getId())
|
.orderByAsc(ScenicPricing::getId)).stream()
|
.map(item -> new GuideReceivingOptionResponse.ServiceOption(item.getId(), item.getServiceType(),
|
item.getDurationMinutes(), item.getMinParticipants(), item.getMaxParticipants()))
|
.collect(Collectors.toList());
|
rows.add(new GuideReceivingOptionResponse.ScenicOption(scenic.getId(), scenic.getScenicName(), points, services));
|
}
|
return new GuideReceivingOptionResponse(premiumEnabled, value(settings.getPreciseDispatchOrderCount()),
|
settings.getPreciseDispatchRating(), rows);
|
}
|
|
@Transactional
|
public GuideReceivingSession start(Long guideId, GuideReceivingStartRequest request) {
|
GuideUser guide = guideUserService.findEnabledById(guideId);
|
String mode = normalizeMode(request.getReceivingMode());
|
OperationScenicSpot scenic = requireAllowedScenic(guide, request.getScenicSpotId());
|
requireMeetingPoint(request.getMeetingPointId(), scenic.getId());
|
Set<String> serviceTypes = new HashSet<>(request.getServiceTypes());
|
if (serviceTypes.size() != request.getServiceTypes().size()) {
|
throw new BusinessException(400, "service type cannot be duplicated");
|
}
|
validateServiceTypes(guide, scenic.getId(), mode, serviceTypes);
|
stopActiveSessions(guideId);
|
|
GuideReceivingSession session = new GuideReceivingSession();
|
session.setGuideId(guideId);
|
session.setScenicSpotId(scenic.getId());
|
session.setMeetingPointId(request.getMeetingPointId());
|
session.setReceivingMode(mode);
|
session.setServiceTypes(String.join(",", serviceTypes));
|
session.setStatus("RECEIVING");
|
session.setCurrentPeopleCount(0);
|
sessionMapper.insert(session);
|
saveRealtimeStatus(guideId, request, scenic.getId());
|
return sessionMapper.selectById(session.getId());
|
}
|
|
public GuideReceivingSession current(Long guideId) {
|
guideUserService.findEnabledById(guideId);
|
return sessionMapper.selectOne(new LambdaQueryWrapper<GuideReceivingSession>()
|
.eq(GuideReceivingSession::getGuideId, guideId)
|
.in(GuideReceivingSession::getStatus, "RECEIVING", "ASSIGNED", "IN_SERVICE")
|
.orderByDesc(GuideReceivingSession::getId).last("LIMIT 1"));
|
}
|
|
@Transactional
|
public void stop(Long guideId) {
|
guideUserService.findEnabledById(guideId);
|
stopActiveSessions(guideId);
|
realtimeStatusMapper.update(null, new LambdaUpdateWrapper<GuideRealtimeStatus>()
|
.eq(GuideRealtimeStatus::getGuideId, guideId).set(GuideRealtimeStatus::getAvailableStatus, 0));
|
}
|
|
@Transactional
|
public void finish(Long guideId) {
|
guideUserService.findEnabledById(guideId);
|
GuideReceivingSession session = sessionMapper.selectOne(new LambdaQueryWrapper<GuideReceivingSession>()
|
.eq(GuideReceivingSession::getGuideId, guideId)
|
.eq(GuideReceivingSession::getStatus, "IN_SERVICE")
|
.orderByDesc(GuideReceivingSession::getId).last("LIMIT 1"));
|
if (session == null) {
|
throw new BusinessException(400, "no active service session");
|
}
|
|
LocalDateTime now = LocalDateTime.now();
|
int orderCount = orderMapper.completeSessionOrders(guideId, session.getId(), now);
|
if (orderCount == 0) {
|
throw new BusinessException(400, "no active service order");
|
}
|
|
sessionMapper.update(null, new LambdaUpdateWrapper<GuideReceivingSession>()
|
.eq(GuideReceivingSession::getId, session.getId())
|
.eq(GuideReceivingSession::getGuideId, guideId)
|
.eq(GuideReceivingSession::getStatus, "IN_SERVICE")
|
.set(GuideReceivingSession::getStatus, "STOPPED"));
|
realtimeStatusMapper.update(null, new LambdaUpdateWrapper<GuideRealtimeStatus>()
|
.eq(GuideRealtimeStatus::getGuideId, guideId)
|
.set(GuideRealtimeStatus::getOnlineStatus, 0)
|
.set(GuideRealtimeStatus::getAvailableStatus, 0));
|
}
|
|
private Set<String> configuredScenicNames(GuideUser guide) {
|
if (guide.getTeamId() == null) {
|
return guideScenicMapper.selectList(new LambdaQueryWrapper<GuideScenicConfig>()
|
.eq(GuideScenicConfig::getGuideId, guide.getId())).stream()
|
.map(GuideScenicConfig::getScenicName).collect(Collectors.toSet());
|
}
|
return teamScenicMapper.selectList(new LambdaQueryWrapper<GuideTeamScenicConfig>()
|
.eq(GuideTeamScenicConfig::getTeamId, guide.getTeamId())).stream()
|
.map(GuideTeamScenicConfig::getScenicName).collect(Collectors.toSet());
|
}
|
|
private OperationScenicSpot requireAllowedScenic(GuideUser guide, Long scenicId) {
|
OperationScenicSpot scenic = scenicMapper.selectOne(new LambdaQueryWrapper<OperationScenicSpot>()
|
.eq(OperationScenicSpot::getId, scenicId).eq(OperationScenicSpot::getStatus, 1));
|
if (scenic == null) throw new BusinessException(404, "scenic spot is unavailable");
|
if (!configuredScenicNames(guide).contains(scenic.getScenicName())) {
|
throw new BusinessException(403, "guide is not configured for this scenic spot");
|
}
|
return scenic;
|
}
|
|
private void requireMeetingPoint(Long meetingPointId, Long scenicId) {
|
long count = meetingPointMapper.selectCount(new LambdaQueryWrapper<ScenicMeetingPoint>()
|
.eq(ScenicMeetingPoint::getId, meetingPointId).eq(ScenicMeetingPoint::getScenicSpotId, scenicId)
|
.eq(ScenicMeetingPoint::getStatus, 1));
|
if (count == 0) throw new BusinessException(400, "meeting point does not belong to scenic spot");
|
}
|
|
private void validateServiceTypes(GuideUser guide, Long scenicId, String mode, Set<String> serviceTypes) {
|
if (serviceTypes.isEmpty()) throw new BusinessException(400, "select at least one service type");
|
Set<String> configured = pricingMapper.selectList(new LambdaQueryWrapper<ScenicPricing>()
|
.eq(ScenicPricing::getScenicSpotId, scenicId)).stream()
|
.map(ScenicPricing::getServiceType).collect(Collectors.toSet());
|
for (String type : serviceTypes) {
|
if (!type.startsWith(mode + "_") || !configured.contains(type)) {
|
throw new BusinessException(400, "service type does not match receiving mode or scenic configuration");
|
}
|
if (type.endsWith("_PREMIUM") && !premiumEnabled(guide, platformSettingsService.getSettings())) {
|
throw new BusinessException(403, "premium service requirements are not met");
|
}
|
}
|
}
|
|
private boolean premiumEnabled(GuideUser guide, PlatformSettingsResponse settings) {
|
if (guide.getAutoDisableWhitelisted() != null && guide.getAutoDisableWhitelisted() == 1) {
|
return true;
|
}
|
long orderCount = guide.getAcceptedOrderCount() == null ? 0 : guide.getAcceptedOrderCount();
|
BigDecimal rating = guide.getRating() == null ? BigDecimal.ZERO : guide.getRating();
|
BigDecimal requiredRating = settings.getPreciseDispatchRating() == null ? BigDecimal.ZERO : settings.getPreciseDispatchRating();
|
return orderCount >= value(settings.getPreciseDispatchOrderCount()) && rating.compareTo(requiredRating) >= 0;
|
}
|
|
private void stopActiveSessions(Long guideId) {
|
sessionMapper.update(null, new LambdaUpdateWrapper<GuideReceivingSession>()
|
.eq(GuideReceivingSession::getGuideId, guideId)
|
.in(GuideReceivingSession::getStatus, "RECEIVING", "ASSIGNED")
|
.set(GuideReceivingSession::getStatus, "STOPPED"));
|
}
|
|
private void saveRealtimeStatus(Long guideId, GuideReceivingStartRequest request, Long scenicId) {
|
GuideRealtimeStatus status = realtimeStatusMapper.selectById(guideId);
|
boolean insert = status == null;
|
if (insert) { status = new GuideRealtimeStatus(); status.setGuideId(guideId); }
|
status.setScenicSpotId(scenicId);
|
status.setMeetingPointId(request.getMeetingPointId());
|
status.setLongitude(request.getLongitude());
|
status.setLatitude(request.getLatitude());
|
status.setOnlineStatus(1);
|
status.setAvailableStatus(1);
|
status.setLastLocationAt(LocalDateTime.now());
|
if (insert) realtimeStatusMapper.insert(status); else realtimeStatusMapper.updateById(status);
|
}
|
|
private String normalizeMode(String mode) {
|
if (!"GROUP".equals(mode) && !"PRIVATE".equals(mode)) throw new BusinessException(400, "invalid receiving mode");
|
return mode;
|
}
|
|
private long value(Integer number) { return number == null ? 0L : number.longValue(); }
|
}
|