puzhibing
2026-09-29 e665bc906ba6ef715acfa55ebe3ad2370154626d
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
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.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.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;
 
    @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("导入数据不能为空");
        }
        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 (!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 + "行车队名称已存在或重复");
            }
            companyNames.add(item.getCompanyName().trim());
        }
        // 批量查询公司名称 -> ID
        Map<String, Integer> companyMap = 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().trim();
            if (!companyMap.containsKey(companyName)) {
                throw new ServiceException("第" + row + "行所属公司不存在:" + companyName);
            }
        }
        LocalDateTime now = LocalDateTime.now();
        for (FleetImportDTO item : items) {
            TFleet fleet = new TFleet();
            fleet.setCompanyId(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);
    }
 
}