package com.ruoyi.other.service.impl;
|
|
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.ruoyi.account.api.feignClient.FleetUnbindClient;
|
import com.ruoyi.account.api.feignClient.VinManagementClient;
|
import com.ruoyi.account.api.dto.VinManagementQueryDTO;
|
import com.ruoyi.account.api.model.TVinManagement;
|
import com.ruoyi.common.core.domain.R;
|
import com.ruoyi.common.core.exception.ServiceException;
|
import com.ruoyi.other.api.domain.TChargeAuthorization;
|
import com.ruoyi.other.api.domain.TFleet;
|
import com.ruoyi.other.api.domain.TGroupCompany;
|
import com.ruoyi.other.api.dto.FleetExportDTO;
|
import com.ruoyi.other.api.dto.FleetImportDTO;
|
import com.ruoyi.other.api.dto.FleetQueryDTO;
|
import com.ruoyi.other.api.feignClient.FleetOrderAggregateClient;
|
import com.ruoyi.other.api.vo.FleetOrderAggregateVO;
|
import com.ruoyi.other.mapper.TChargeAuthorizationMapper;
|
import com.ruoyi.other.mapper.TFleetMapper;
|
import com.ruoyi.other.service.CompanyAccountAccessService;
|
import com.ruoyi.other.service.TFleetService;
|
import com.ruoyi.other.service.TGroupCompanyService;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.util.StringUtils;
|
|
import javax.annotation.Resource;
|
import java.math.BigDecimal;
|
import java.time.LocalDateTime;
|
import java.util.ArrayList;
|
import java.util.Collections;
|
import java.util.HashMap;
|
import java.util.HashSet;
|
import java.util.LinkedHashSet;
|
import java.util.List;
|
import java.util.Map;
|
import java.util.Objects;
|
import java.util.Set;
|
import java.util.stream.Collectors;
|
|
/**
|
* <p>
|
* 车队管理 服务实现类
|
* </p>
|
*
|
* @author mxcd
|
* @since 2026-09-22
|
*/
|
@Service
|
public class TFleetServiceImpl extends ServiceImpl<TFleetMapper, TFleet> implements TFleetService {
|
|
private static final int ENABLED = 1;
|
private static final int DISABLED = 0;
|
|
@Resource
|
private TGroupCompanyService groupCompanyService;
|
|
@Resource
|
private VinManagementClient vinManagementClient;
|
|
@Resource
|
private FleetOrderAggregateClient fleetOrderAggregateClient;
|
|
@Resource
|
private TChargeAuthorizationMapper chargeAuthorizationMapper;
|
|
@Resource
|
private FleetUnbindClient fleetUnbindClient;
|
|
@Resource
|
private CompanyAccountAccessService companyAccountAccessService;
|
|
@Override
|
public Page<TVinManagement> selectVinPage(Integer fleetId, Integer current, Integer size) {
|
if (fleetId == null) {
|
throw new ServiceException("车队ID不能为空");
|
}
|
TFleet fleet = getById(fleetId);
|
if (fleet == null) {
|
throw new ServiceException("车队不存在或已删除");
|
}
|
VinManagementQueryDTO query = new VinManagementQueryDTO();
|
query.setFleetId(fleetId);
|
query.setPageCurr(current == null || current < 1 ? 1 : current);
|
query.setPageSize(size == null || size < 1 ? 10 : size);
|
R<Page<TVinManagement>> response = vinManagementClient.getVinPageByFleet(query);
|
if (response == null || !R.isSuccess(response) || response.getData() == null) {
|
throw new ServiceException("VIN码车辆查询失败:" + (response == null ? "服务无响应" : response.getMsg()));
|
}
|
fillAvailableBalance(fleet.getCompanyId(), response.getData());
|
return response.getData();
|
}
|
|
/**
|
* 填充VIN码车辆可用余额(车队详情弹窗展示):
|
* 公司账户模式 2=分配模式 → VIN码自身余额;其余(1=共享/历史null)→ 公司当前可用余额;
|
* 公司不存在或余额为空时保持 null,前端显示 '-'。
|
*/
|
private void fillAvailableBalance(Integer companyId, Page<TVinManagement> page) {
|
if (page == null || page.getRecords() == null || page.getRecords().isEmpty()) {
|
return;
|
}
|
TGroupCompany company = companyId == null ? null : groupCompanyService.getById(companyId);
|
boolean allocation = company != null && Integer.valueOf(2).equals(company.getAccountMode());
|
BigDecimal companyBalance = company == null ? null : company.getAvailableBalance();
|
for (TVinManagement item : page.getRecords()) {
|
item.setAvailableBalance(allocation ? item.getBalance() : companyBalance);
|
}
|
}
|
|
@Override
|
@Transactional(rollbackFor = Exception.class)
|
public String importFleets(List<FleetImportDTO> items) {
|
if (items == null || items.isEmpty()) {
|
throw new ServiceException("导入数据不能为空");
|
}
|
// 公司账号导入车队时强制归属本公司,Excel中的所属公司仅作展示信息
|
Integer lockedCompanyId = companyAccountAccessService.getCurrentCompanyId();
|
Set<String> fleetNames = new HashSet<>();
|
Set<String> companyNames = new HashSet<>();
|
int row = 1;
|
for (FleetImportDTO item : items) {
|
row++;
|
if (item == null || !StringUtils.hasText(item.getFleetName())) {
|
throw new ServiceException("第" + row + "行车队名称不能为空");
|
}
|
String fleetName = item.getFleetName().trim();
|
if (fleetName.length() > 128) {
|
throw new ServiceException("第" + row + "行车队名称不能超过128个字符");
|
}
|
if (lockedCompanyId == null && !StringUtils.hasText(item.getCompanyName())) {
|
throw new ServiceException("第" + row + "行所属公司不能为空");
|
}
|
parseStatus(item.getStatus(), row);
|
if (!fleetNames.add(fleetName) || lambdaQuery().eq(TFleet::getFleetName, fleetName).count() > 0) {
|
throw new ServiceException("第" + row + "行车队名称已存在或重复");
|
}
|
if (lockedCompanyId == null && StringUtils.hasText(item.getCompanyName())) {
|
companyNames.add(item.getCompanyName().trim());
|
}
|
}
|
// 批量查询公司名称 -> ID
|
Map<String, Integer> companyMap = companyNames.isEmpty() ? new HashMap<>() : groupCompanyService.lambdaQuery()
|
.in(TGroupCompany::getName, companyNames)
|
.list()
|
.stream()
|
.collect(Collectors.toMap(TGroupCompany::getName, TGroupCompany::getId, (a, b) -> a));
|
row = 1;
|
for (FleetImportDTO item : items) {
|
row++;
|
String companyName = item.getCompanyName() == null ? null : item.getCompanyName().trim();
|
if (lockedCompanyId == null && (companyName == null || !companyMap.containsKey(companyName))) {
|
throw new ServiceException("第" + row + "行所属公司不存在:" + companyName);
|
}
|
}
|
LocalDateTime now = LocalDateTime.now();
|
for (FleetImportDTO item : items) {
|
TFleet fleet = new TFleet();
|
fleet.setCompanyId(lockedCompanyId != null ? lockedCompanyId : companyMap.get(item.getCompanyName().trim()));
|
fleet.setFleetName(item.getFleetName().trim());
|
fleet.setContactName(trim(item.getContactName()));
|
fleet.setContactPhone(trim(item.getContactPhone()));
|
fleet.setStatus(parseStatus(item.getStatus(), 0));
|
fleet.setDelFlag(false);
|
fleet.setCreateTime(now);
|
save(fleet);
|
}
|
return "成功导入" + items.size() + "条车队数据";
|
}
|
|
@Override
|
public List<FleetExportDTO> selectExportList(FleetQueryDTO query) {
|
FleetQueryDTO safe = query == null ? new FleetQueryDTO() : query;
|
List<TFleet> fleets = lambdaQuery()
|
.like(StringUtils.hasText(safe.getFleetName()), TFleet::getFleetName, safe.getFleetName())
|
.eq(safe.getCompanyId() != null, TFleet::getCompanyId, safe.getCompanyId())
|
.eq(safe.getStatus() != null, TFleet::getStatus, safe.getStatus())
|
.orderByDesc(TFleet::getId)
|
.list();
|
if (fleets == null || fleets.isEmpty()) {
|
return new ArrayList<>();
|
}
|
Set<Integer> companyIds = fleets.stream()
|
.map(TFleet::getCompanyId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Integer, String> nameMap = companyIds.isEmpty() ? Collections.emptyMap()
|
: groupCompanyService.listByIds(companyIds).stream()
|
.collect(Collectors.toMap(TGroupCompany::getId, TGroupCompany::getName, (a, b) -> a));
|
// 复用与列表相同的聚合逻辑填充5列
|
fillAggregates(fleets);
|
return fleets.stream().map(item -> {
|
FleetExportDTO dto = new FleetExportDTO();
|
dto.setFleetName(item.getFleetName());
|
dto.setCompanyName(nameMap.get(item.getCompanyId()));
|
dto.setVinCount(item.getVinCount() == null ? null : String.valueOf(item.getVinCount()));
|
dto.setStationCount(item.getStationCount() == null ? null : String.valueOf(item.getStationCount()));
|
dto.setCumulativeCharge(item.getCumulativeCharge() == null ? null : item.getCumulativeCharge().toPlainString());
|
dto.setCumulativeElectrovalence(item.getCumulativeElectrovalence() == null ? null : item.getCumulativeElectrovalence().toPlainString());
|
dto.setCumulativeServiceCharge(item.getCumulativeServiceCharge() == null ? null : item.getCumulativeServiceCharge().toPlainString());
|
dto.setStatus(item.getStatus() == null ? null : String.valueOf(item.getStatus()));
|
return dto;
|
}).collect(Collectors.toList());
|
}
|
|
/**
|
* 填充车队5列聚合(VIN车辆数、可充电站数、累计充电量/电费/服务费)
|
* <p>VIN/订单聚合通过远程调用(feign fallback 已兜底返回 0/空),可充电站数本地查询 t_charge_authorization.site_ids 去重计数(过滤 del_flag=0);
|
* t_site.status 过滤需跨库(charging_pile_service)暂不实施(保持本服务自治);任一远程调用异常不会影响整体加载。
|
*/
|
public void fillAggregates(List<TFleet> fleets) {
|
if (fleets == null || fleets.isEmpty()) {
|
return;
|
}
|
// 1. 可充电站数:按 companyId 聚合 t_charge_authorization.site_ids 去重计数
|
Set<Integer> companyIds = fleets.stream()
|
.map(TFleet::getCompanyId)
|
.filter(Objects::nonNull)
|
.collect(Collectors.toSet());
|
Map<Integer, Long> stationCountMap = companyIds.isEmpty()
|
? Collections.emptyMap()
|
: chargeAuthorizationMapper.selectList(Wrappers.<TChargeAuthorization>lambdaQuery()
|
.in(TChargeAuthorization::getCompanyId, companyIds)
|
.eq(TChargeAuthorization::getDelFlag, 0))
|
.stream()
|
.collect(Collectors.toMap(
|
TChargeAuthorization::getCompanyId,
|
auth -> countSiteIds(auth.getSiteIds()),
|
(a, b) -> a));
|
// 2. 遍历填充
|
for (TFleet fleet : fleets) {
|
if (fleet.getId() == null) {
|
continue;
|
}
|
// VIN车辆数(远程调用,软失败 fallback 已处理)
|
try {
|
R<Long> response = vinManagementClient.countByFleet(fleet.getId());
|
fleet.setVinCount(response == null || !R.isSuccess(response) ? 0L : response.getData());
|
} catch (Exception ex) {
|
fleet.setVinCount(0L);
|
}
|
// 可充电站数(本地查询)
|
fleet.setStationCount(fleet.getCompanyId() == null ? 0L : stationCountMap.getOrDefault(fleet.getCompanyId(), 0L));
|
// 累计三列(远程调用,软失败 fallback 已处理返回 0)
|
try {
|
R<FleetOrderAggregateVO> response = fleetOrderAggregateClient.aggregateByFleet(fleet.getId());
|
FleetOrderAggregateVO data = response == null || !R.isSuccess(response) ? null : response.getData();
|
fleet.setCumulativeCharge(data == null || data.getCumulativeCharge() == null ? BigDecimal.ZERO : data.getCumulativeCharge());
|
fleet.setCumulativeElectrovalence(data == null || data.getCumulativeElectrovalence() == null ? BigDecimal.ZERO : data.getCumulativeElectrovalence());
|
fleet.setCumulativeServiceCharge(data == null || data.getCumulativeServiceCharge() == null ? BigDecimal.ZERO : data.getCumulativeServiceCharge());
|
} catch (Exception ex) {
|
fleet.setCumulativeCharge(BigDecimal.ZERO);
|
fleet.setCumulativeElectrovalence(BigDecimal.ZERO);
|
fleet.setCumulativeServiceCharge(BigDecimal.ZERO);
|
}
|
}
|
}
|
|
/**
|
* 统计授权 site_ids 逗号字符串的去重 ID 数(容错:null/空/末尾逗号/连续逗号)
|
*/
|
private long countSiteIds(String siteIds) {
|
if (!StringUtils.hasText(siteIds)) {
|
return 0L;
|
}
|
Set<String> set = new LinkedHashSet<>();
|
for (String token : siteIds.split(",")) {
|
String trimmed = token == null ? "" : token.trim();
|
if (!trimmed.isEmpty()) {
|
set.add(trimmed);
|
}
|
}
|
return set.size();
|
}
|
|
private String trim(String value) {
|
return value == null ? null : value.trim();
|
}
|
|
private Integer parseStatus(String value, int row) {
|
if (!StringUtils.hasText(value) || "1".equals(value.trim()) || "启用".equals(value.trim())) {
|
return ENABLED;
|
}
|
if ("0".equals(value.trim()) || "停用".equals(value.trim())) {
|
return DISABLED;
|
}
|
throw new ServiceException((row > 0 ? "第" + row + "行" : "") + "启用状态错误");
|
}
|
|
@Override
|
public boolean deleteFleets(List<Integer> ids) {
|
if (ids == null || ids.isEmpty()) {
|
throw new ServiceException("请选择需要删除的车队");
|
}
|
List<Integer> distinct = ids.stream().filter(Objects::nonNull).distinct().collect(Collectors.toList());
|
if (distinct.isEmpty()) {
|
throw new ServiceException("请选择需要删除的车队");
|
}
|
if (listByIds(distinct).size() != distinct.size()) {
|
throw new ServiceException("部分车队不存在或已删除");
|
}
|
// 先解绑该车队下的 VIN码/充电卡/白名单(跨服务无分布式事务:解绑失败即中止删除,两侧数据保持一致;
|
// 解绑成功后删除失败仅剩车队未删,可重试删除,不会产生已删车队挂靠的脏引用)
|
try {
|
R<Integer> response = fleetUnbindClient.unbindByFleetIds(distinct);
|
if (response == null || !R.isSuccess(response)) {
|
throw new ServiceException("车队关联数据解绑失败:" + (response == null ? "服务无响应" : response.getMsg()));
|
}
|
} catch (ServiceException ex) {
|
throw ex;
|
} catch (Exception ex) {
|
throw new ServiceException("车队关联数据解绑失败,请稍后重试");
|
}
|
return removeByIds(distinct);
|
}
|
|
}
|