liujie
2026-09-14 82a8bc62d5b9d5d607f6952329952b17eda2f0a0
backend/src/main/java/com/tourguide/modules/user/service/GuideTeamService.java
@@ -4,17 +4,30 @@
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.tourguide.common.api.PageResponse;
import com.tourguide.common.exception.BusinessException;
import com.tourguide.modules.user.dto.GuideRegistrationTeamOptionResponse;
import com.tourguide.modules.user.dto.GuideTeamResponse;
import com.tourguide.modules.user.dto.GuideTeamCollectionGuideResponse;
import com.tourguide.modules.user.dto.GuideTeamScenicRequest;
import com.tourguide.modules.user.dto.GuideTeamSaveRequest;
import com.tourguide.modules.user.entity.GuideTeam;
import com.tourguide.modules.user.entity.GuideTeamScenicConfig;
import com.tourguide.modules.user.entity.GuideUser;
import com.tourguide.modules.user.mapper.GuideTeamMapper;
import com.tourguide.modules.user.mapper.GuideTeamScenicConfigMapper;
import com.tourguide.modules.user.mapper.GuideUserMapper;
import com.tourguide.modules.finance.mapper.GuideWithdrawalMapper;
import com.tourguide.modules.operation.entity.OperationCity;
import com.tourguide.modules.operation.entity.OperationScenicSpot;
import com.tourguide.modules.operation.entity.ScenicMeetingPoint;
import com.tourguide.modules.operation.mapper.OperationCityMapper;
import com.tourguide.modules.operation.mapper.OperationScenicSpotMapper;
import com.tourguide.modules.operation.mapper.ScenicMeetingPointMapper;
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.time.LocalDate;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
@@ -25,10 +38,31 @@
public class GuideTeamService {
    private final GuideTeamMapper teamMapper;
    private final GuideTeamScenicConfigMapper scenicConfigMapper;
    private final GuideUserMapper guideUserMapper;
    private final GuideWithdrawalMapper withdrawalMapper;
    private final OperationScenicSpotMapper operationScenicSpotMapper;
    private final OperationCityMapper operationCityMapper;
    private final ScenicMeetingPointMapper meetingPointMapper;
    public GuideTeamService(GuideTeamMapper teamMapper, GuideTeamScenicConfigMapper scenicConfigMapper) {
    public GuideTeamService(GuideTeamMapper teamMapper, GuideTeamScenicConfigMapper scenicConfigMapper,
                            GuideUserMapper guideUserMapper, GuideWithdrawalMapper withdrawalMapper) {
        this(teamMapper, scenicConfigMapper, guideUserMapper, withdrawalMapper,
                null, null, null);
    }
    @Autowired
    public GuideTeamService(GuideTeamMapper teamMapper, GuideTeamScenicConfigMapper scenicConfigMapper,
                            GuideUserMapper guideUserMapper, GuideWithdrawalMapper withdrawalMapper,
                            OperationScenicSpotMapper operationScenicSpotMapper,
                            OperationCityMapper operationCityMapper,
                            ScenicMeetingPointMapper meetingPointMapper) {
        this.teamMapper = teamMapper;
        this.scenicConfigMapper = scenicConfigMapper;
        this.guideUserMapper = guideUserMapper;
        this.withdrawalMapper = withdrawalMapper;
        this.operationScenicSpotMapper = operationScenicSpotMapper;
        this.operationCityMapper = operationCityMapper;
        this.meetingPointMapper = meetingPointMapper;
    }
    public GuideTeamResponse detail(Long id) {
@@ -58,13 +92,44 @@
                result.getTotal(), result.getCurrent(), result.getSize());
    }
    public List<GuideRegistrationTeamOptionResponse> registrationOptions() {
        List<GuideRegistrationTeamOptionResponse> options = new ArrayList<GuideRegistrationTeamOptionResponse>();
        options.add(GuideRegistrationTeamOptionResponse.personal());
        List<GuideTeam> teams = teamMapper.selectList(new LambdaQueryWrapper<GuideTeam>()
                .eq(GuideTeam::getStatus, 1)
                .orderByAsc(GuideTeam::getTeamName));
        for (GuideTeam team : teams) {
            options.add(GuideRegistrationTeamOptionResponse.team(team));
        }
        return options;
    }
    public List<GuideTeamCollectionGuideResponse> collectionGuideOptions(Long teamId) {
        requireTeam(teamId);
        return guideUserMapper.selectList(new LambdaQueryWrapper<GuideUser>()
                        .eq(GuideUser::getTeamId, teamId)
                        .orderByAsc(GuideUser::getRealName)
                        .orderByAsc(GuideUser::getId))
                .stream()
                .map(guide -> new GuideTeamCollectionGuideResponse(
                        guide, withdrawalMapper.countActiveByGuideId(guide.getId()) > 0))
                .collect(Collectors.toList());
    }
    @Transactional
    public GuideTeamResponse create(GuideTeamSaveRequest request) {
        ensureNameAvailable(request.getTeamName(), null);
        GuideTeam team = new GuideTeam();
        apply(team, request);
        team.setCollectionGuideId(null);
        team.setBalance(java.math.BigDecimal.ZERO);
        team.setStatus(1);
        teamMapper.insert(team);
        if (request.getCollectionGuideId() != null) {
            validateCollectionGuide(team.getId(), request.getCollectionGuideId());
            team.setCollectionGuideId(request.getCollectionGuideId());
            teamMapper.updateById(team);
        }
        replaceScenicConfigs(team.getId(), request.getScenicConfigs());
        return detail(team.getId());
    }
@@ -73,6 +138,9 @@
    public GuideTeamResponse update(Long id, GuideTeamSaveRequest request) {
        GuideTeam team = requireTeam(id);
        ensureNameAvailable(request.getTeamName(), id);
        if (!java.util.Objects.equals(team.getCollectionGuideId(), request.getCollectionGuideId())) {
            validateCollectionGuide(id, request.getCollectionGuideId());
        }
        apply(team, request);
        teamMapper.updateById(team);
        replaceScenicConfigs(id, request.getScenicConfigs());
@@ -120,6 +188,24 @@
        team.setCommissionRatio(request.getCommissionRatio());
        team.setWechatOpenId(trimToNull(request.getWechatOpenId()));
        team.setMerchantId(trimToNull(request.getMerchantId()));
        team.setCollectionGuideId(request.getCollectionGuideId());
    }
    private void validateCollectionGuide(Long teamId, Long guideId) {
        if (guideId == null) return;
        GuideUser guide = guideUserMapper.selectForUpdate(guideId);
        if (guide == null || guide.getStatus() == null || guide.getStatus() != 1) {
            throw new BusinessException(409, "收款导游不存在或已停用");
        }
        if (!teamId.equals(guide.getTeamId())) {
            throw new BusinessException(409, "收款导游必须属于当前讲解队");
        }
        if (guide.getBalance() != null && guide.getBalance().signum() != 0) {
            throw new BusinessException(409, "导游账户余额必须为0才能配置为收款导游");
        }
        if (withdrawalMapper.countActiveByGuideId(guideId) > 0) {
            throw new BusinessException(409, "导游存在进行中的提现单,不能配置为收款导游");
        }
    }
    private void replaceScenicConfigs(Long teamId, List<GuideTeamScenicRequest> requests) {
@@ -129,6 +215,7 @@
        }
        Set<String> scenicNames = new HashSet<String>();
        for (GuideTeamScenicRequest request : requests) {
            validateManagedScenic(request);
            String scenicName = request.getScenicName().trim();
            if (!scenicNames.add(scenicName)) {
                throw new BusinessException(409, "同一讲解队不能重复配置相同景区");
@@ -142,6 +229,43 @@
        }
    }
    private void validateManagedScenic(GuideTeamScenicRequest request) {
        if (operationScenicSpotMapper == null || operationCityMapper == null || meetingPointMapper == null) {
            throw new BusinessException(500, "景区配置校验服务未初始化");
        }
        OperationCity city = operationCityMapper.selectOne(new LambdaQueryWrapper<OperationCity>()
                .eq(OperationCity::getCityName, request.getCityName().trim())
                .eq(OperationCity::getStatus, 1)
                .last("LIMIT 1"));
        if (city == null) {
            throw new BusinessException(400, "所选景区城市不存在或已停用");
        }
        OperationScenicSpot scenicSpot = operationScenicSpotMapper.selectOne(
                new LambdaQueryWrapper<OperationScenicSpot>()
                        .eq(OperationScenicSpot::getScenicName, request.getScenicName().trim())
                        .eq(OperationScenicSpot::getCityId, city.getId())
                        .eq(OperationScenicSpot::getStatus, 1)
                        .last("LIMIT 1"));
        if (scenicSpot == null) {
            throw new BusinessException(400, "所选景区不存在或已停用");
        }
        Set<String> allowedMeetingPoints = meetingPointMapper.selectList(
                        new LambdaQueryWrapper<ScenicMeetingPoint>()
                                .eq(ScenicMeetingPoint::getScenicSpotId, scenicSpot.getId())
                                .eq(ScenicMeetingPoint::getStatus, 1))
                .stream().map(ScenicMeetingPoint::getPointName).collect(Collectors.toSet());
        List<String> selectedMeetingPoints = java.util.Arrays.stream(request.getMeetingPoints().split("[,,]"))
                .map(String::trim).filter(StringUtils::hasText).collect(Collectors.toList());
        if (selectedMeetingPoints.isEmpty()
                || selectedMeetingPoints.size() != new HashSet<String>(selectedMeetingPoints).size()
                || !allowedMeetingPoints.containsAll(selectedMeetingPoints)) {
            throw new BusinessException(400, "汇合点必须从所选景区的启用汇合点中选择");
        }
        request.setScenicName(scenicSpot.getScenicName());
        request.setCityName(city.getCityName());
        request.setMeetingPoints(String.join(",", selectedMeetingPoints));
    }
    private String trimToNull(String value) {
        return StringUtils.hasText(value) ? value.trim() : null;
    }