| | |
| | | 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; |
| | |
| | | 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) { |
| | |
| | | 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()); |
| | | } |
| | |
| | | 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()); |
| | |
| | | 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) { |
| | |
| | | } |
| | | 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, "同一讲解队不能重复配置相同景区"); |
| | |
| | | } |
| | | } |
| | | |
| | | 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; |
| | | } |