package com.tourguide.modules.user.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.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;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
@Service
|
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,
|
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) {
|
GuideTeam team = requireTeam(id);
|
List<GuideTeamScenicConfig> scenicConfigs = scenicConfigMapper.selectList(
|
new LambdaQueryWrapper<GuideTeamScenicConfig>()
|
.eq(GuideTeamScenicConfig::getTeamId, id)
|
.orderByAsc(GuideTeamScenicConfig::getId));
|
return GuideTeamResponse.from(team, scenicConfigs);
|
}
|
|
public PageResponse<GuideTeamResponse> page(long page, long size, String keyword, Integer status,
|
LocalDate startDate, LocalDate endDate) {
|
LambdaQueryWrapper<GuideTeam> query = new LambdaQueryWrapper<GuideTeam>();
|
if (StringUtils.hasText(keyword)) {
|
String searchKeyword = keyword.trim();
|
query.and(wrapper -> wrapper.like(GuideTeam::getTeamName, searchKeyword)
|
.or().like(GuideTeam::getContactPhone, searchKeyword));
|
}
|
query.eq(status != null, GuideTeam::getStatus, status)
|
.ge(startDate != null, GuideTeam::getCreatedAt, startDate == null ? null : startDate.atStartOfDay())
|
.lt(endDate != null, GuideTeam::getCreatedAt, endDate == null ? null : endDate.plusDays(1).atStartOfDay())
|
.orderByDesc(GuideTeam::getId);
|
Page<GuideTeam> result = teamMapper.selectPage(new Page<GuideTeam>(page, size), query);
|
return new PageResponse<GuideTeamResponse>(
|
result.getRecords().stream().map(GuideTeamResponse::from).collect(Collectors.toList()),
|
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());
|
}
|
|
@Transactional
|
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());
|
return detail(id);
|
}
|
|
@Transactional
|
public void updateStatus(Long id, Integer status) {
|
GuideTeam team = requireTeam(id);
|
team.setStatus(status);
|
teamMapper.updateById(team);
|
}
|
|
private GuideTeam requireTeam(Long id) {
|
GuideTeam team = teamMapper.selectById(id);
|
if (team == null) {
|
throw new BusinessException(404, "讲解队不存在");
|
}
|
return team;
|
}
|
|
private void ensureNameAvailable(String teamName, Long excludedId) {
|
LambdaQueryWrapper<GuideTeam> query = new LambdaQueryWrapper<GuideTeam>()
|
.eq(GuideTeam::getTeamName, teamName.trim())
|
.ne(excludedId != null, GuideTeam::getId, excludedId);
|
if (teamMapper.selectCount(query) > 0) {
|
throw new BusinessException(409, "讲解队名称已存在");
|
}
|
}
|
|
private void apply(GuideTeam team, GuideTeamSaveRequest request) {
|
List<GuideTeamScenicRequest> scenicConfigs = request.getScenicConfigs() == null
|
? Collections.<GuideTeamScenicRequest>emptyList()
|
: request.getScenicConfigs();
|
team.setTeamName(request.getTeamName().trim());
|
team.setContactPhone(request.getContactPhone().trim());
|
team.setOperatingArea(scenicConfigs.stream()
|
.map(GuideTeamScenicRequest::getScenicName)
|
.filter(StringUtils::hasText)
|
.map(String::trim)
|
.distinct()
|
.collect(Collectors.joining(",")));
|
team.setCompanyName(trimToNull(request.getCompanyName()));
|
team.setUnifiedSocialCreditCode(trimToNull(request.getUnifiedSocialCreditCode()));
|
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) {
|
scenicConfigMapper.markDeletedByTeamId(teamId);
|
if (requests == null) {
|
return;
|
}
|
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, "同一讲解队不能重复配置相同景区");
|
}
|
GuideTeamScenicConfig config = new GuideTeamScenicConfig();
|
config.setTeamId(teamId);
|
config.setScenicName(scenicName);
|
config.setCityName(request.getCityName().trim());
|
config.setMeetingPoints(request.getMeetingPoints().trim());
|
scenicConfigMapper.upsert(config);
|
}
|
}
|
|
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;
|
}
|
}
|