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; /** *

* 车队管理 服务实现类 *

* * @author mxcd * @since 2026-09-22 */ @Service public class TFleetServiceImpl extends ServiceImpl 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 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> 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 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 items) { if (items == null || items.isEmpty()) { throw new ServiceException("导入数据不能为空"); } // 公司账号导入车队时强制归属本公司,Excel中的所属公司仅作展示信息 Integer lockedCompanyId = companyAccountAccessService.getCurrentCompanyId(); Set fleetNames = new HashSet<>(); Set 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 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 selectExportList(FleetQueryDTO query) { FleetQueryDTO safe = query == null ? new FleetQueryDTO() : query; List 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 companyIds = fleets.stream() .map(TFleet::getCompanyId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map 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车辆数、可充电站数、累计充电量/电费/服务费) *

VIN/订单聚合通过远程调用(feign fallback 已兜底返回 0/空),可充电站数本地查询 t_charge_authorization.site_ids 去重计数(过滤 del_flag=0); * t_site.status 过滤需跨库(charging_pile_service)暂不实施(保持本服务自治);任一远程调用异常不会影响整体加载。 */ public void fillAggregates(List fleets) { if (fleets == null || fleets.isEmpty()) { return; } // 1. 可充电站数:按 companyId 聚合 t_charge_authorization.site_ids 去重计数 Set companyIds = fleets.stream() .map(TFleet::getCompanyId) .filter(Objects::nonNull) .collect(Collectors.toSet()); Map stationCountMap = companyIds.isEmpty() ? Collections.emptyMap() : chargeAuthorizationMapper.selectList(Wrappers.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 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 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 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 ids) { if (ids == null || ids.isEmpty()) { throw new ServiceException("请选择需要删除的车队"); } List 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 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); } }