Commit f461431bb42fda0f05633996bd9f716c889b0249

Authored by 王富生
1 parent 96792960

巡查计划明细及巡查计划创建优化

urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java
1 -package com.zteits.urbanops.module.garden.service.costfee;  
2 -  
3 -import com.fasterxml.jackson.core.JsonProcessingException;  
4 -import com.fasterxml.jackson.databind.ObjectMapper;  
5 -import com.zteits.urbanops.framework.security.core.LoginUser;  
6 -import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController;  
7 -import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.*;  
8 -import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO;  
9 -import com.zteits.urbanops.module.garden.dal.dataobject.mechanicalcost.MechanicalCostDO;  
10 -import com.zteits.urbanops.module.garden.dal.dataobject.personalcost.PersonalCostDO;  
11 -import com.zteits.urbanops.module.garden.dal.dataobject.rentalmechanicalcost.RentalMechanicalCostDO;  
12 -import com.zteits.urbanops.module.garden.dal.dataobject.wasterecord.WasteRecordDO;  
13 -import com.zteits.urbanops.module.garden.dal.dataobject.waterfee.WaterFeeDO;  
14 -import com.zteits.urbanops.module.garden.dal.mysql.departmentcost.DepartmentCostMapper;  
15 -import com.zteits.urbanops.module.garden.dal.mysql.mechanicalcost.MechanicalCostMapper;  
16 -import com.zteits.urbanops.module.garden.dal.mysql.personalcost.PersonalCostMapper;  
17 -import com.zteits.urbanops.module.garden.dal.mysql.rentalmechanicalcost.RentalMechanicalCostMapper;  
18 -import com.zteits.urbanops.module.garden.dal.mysql.wasterecord.WasteRecordMapper;  
19 -import com.zteits.urbanops.module.garden.dal.mysql.waterfee.WaterFeeMapper;  
20 -import com.zteits.urbanops.module.garden.enums.AssignTasksConstants;  
21 -import com.zteits.urbanops.module.garden.enums.CostFeeConstants;  
22 -import com.zteits.urbanops.module.garden.enums.TemplateEnum;  
23 -import com.zteits.urbanops.module.system.api.dept.DeptApi;  
24 -import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO;  
25 -import jakarta.annotation.Resource;  
26 -import jakarta.validation.ConstraintViolation;  
27 -import jakarta.validation.ConstraintViolationException;  
28 -import jakarta.validation.Validator;  
29 -import lombok.extern.slf4j.Slf4j;  
30 -import org.apache.commons.lang3.StringUtils;  
31 -import org.springframework.beans.BeanUtils;  
32 -import org.springframework.beans.factory.annotation.Autowired;  
33 -import org.springframework.data.redis.core.BoundHashOperations;  
34 -import org.springframework.data.redis.core.RedisTemplate;  
35 -import org.springframework.stereotype.Service;  
36 -import org.springframework.transaction.annotation.Transactional;  
37 -import org.springframework.util.CollectionUtils;  
38 -  
39 -import java.io.IOException;  
40 -import java.time.LocalDate;  
41 -import java.util.*;  
42 -import java.util.concurrent.TimeUnit;  
43 -import java.util.function.Function;  
44 -import java.util.stream.Collectors;  
45 -import java.util.stream.IntStream;  
46 -  
47 -import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;  
48 -import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception;  
49 -import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception0;  
50 -import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.*;  
51 -import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.COST_FEE_NOT_EXISTS_001;  
52 -  
53 -/**  
54 - * @Classname CostFeeService  
55 - * @Description 模板导入公共实现  
56 - * @Date 2025/7/20 15:12  
57 - * @Created by wangqian  
58 - */  
59 -@Service  
60 -@Slf4j  
61 -public class CostFeeServiceImpl implements CostFeeService {  
62 -  
63 - // Redis相关配置  
64 - private static final String REDIS_KEY_PREFIX = "excel:import:";  
65 - private static final long EXPIRE_TIME = 30; // 预览数据过期时间:30分钟  
66 - private static final TimeUnit EXPIRE_UNIT = TimeUnit.MINUTES;  
67 - @Autowired  
68 - private RedisTemplate<String, String> redisTemplate;  
69 - @Autowired  
70 - private ObjectMapper objectMapper;  
71 - @Resource  
72 - private Validator validator;  
73 - @Resource  
74 - private DeptApi deptApi;  
75 -  
76 - @Autowired  
77 - private WaterFeeMapper waterFeeMapper;  
78 - @Autowired  
79 - private PersonalCostMapper personalCostMapper;  
80 - @Autowired  
81 - private DepartmentCostMapper departmentCostMapper;  
82 - @Autowired  
83 - private MechanicalCostMapper mechanicalCostMapper;  
84 - @Autowired  
85 - private RentalMechanicalCostMapper rentalMechanicalCostMapper;  
86 - @Autowired  
87 - private WasteRecordMapper wasteRecordMapper;  
88 -  
89 -  
90 - @Override  
91 - /**  
92 - * 保存数据到Redis  
93 - */  
94 - public String saveDataToRedis(List<?> dataList, String templateName){  
95 - String batchId = UUID.randomUUID().toString().replace("-", "");  
96 - String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;  
97 -  
98 - // 存储总记录数  
99 - redisTemplate.opsForValue().set(  
100 - redisKey + ":total",  
101 - String.valueOf(dataList.size()),  
102 - EXPIRE_TIME,  
103 - EXPIRE_UNIT  
104 - );  
105 -  
106 - // 使用Hash存储每条数据(key:索引,value:JSON字符串)  
107 - BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");  
108 - try {  
109 - for (int i = 0; i < dataList.size(); i++) {  
110 - String jsonData = objectMapper.writeValueAsString(dataList.get(i));  
111 - hashOps.put(String.valueOf(i), jsonData);  
112 - }  
113 - } catch (JsonProcessingException e) {  
114 - log.error(e.getMessage());  
115 - }  
116 - // 设置Hash过期时间  
117 - hashOps.expire(EXPIRE_TIME, EXPIRE_UNIT);  
118 -  
119 - log.info("数据保存到Redis成功,批次ID:{},记录数:{}", batchId, dataList.size());  
120 - return batchId;  
121 - }  
122 -  
123 - @Override  
124 - @Transactional  
125 - public int submitImport(String batchId, String templateName, String operator){  
126 - // 模板类型校验  
127 - TemplateEnum template = TemplateEnum.getByCode(templateName);  
128 - Class<?> importClass = template.getImportClass();  
129 - if (importClass == null) {  
130 - throw exception0(BAD_REQUEST.getCode(),"导入模板配置异常");  
131 - }  
132 -  
133 - // 分页参数设置  
134 - int pageNum = 1;  
135 - final int pageSize = 100000; // 每页处理条数,可根据实际情况调整  
136 - int totalImported = 0; // 总导入数量累计  
137 -  
138 - try {  
139 - while (true) {  
140 - // 分页从Redis获取数据  
141 - CostFeeController.PageResult<?> pageResult = getPagedDataFromRedis(  
142 - batchId, templateName, pageNum, pageSize, importClass);  
143 - List<?> dataList = pageResult.getData();  
144 -  
145 - // 如果当前页无数据,说明已处理完毕  
146 - if (dataList == null || dataList.isEmpty()) {  
147 - // 首次分页就无数据,抛出异常  
148 - if (pageNum == 1) {  
149 - throw exception0(BAD_REQUEST.getCode(),"没有可导入的数据或数据已过期");  
150 - }  
151 - break;  
152 - }  
153 -  
154 - // 业务校验(每页单独校验)  
155 - //validateBusinessData(dataList, template);  
156 -  
157 - // 调用业务服务执行本页数据导入  
158 - int currentPageCount = dataList.size();  
159 - insertData(template, dataList, operator);  
160 - totalImported += currentPageCount;  
161 -  
162 - // 打印分页处理日志(可选)  
163 - log.info("批次[{}]第{}页导入完成,本页{}条,累计{}条",  
164 - batchId, pageNum, currentPageCount, totalImported);  
165 -  
166 - // 准备下一页  
167 - pageNum++;  
168 -  
169 - // 如果当前页数据小于页大小,说明已到最后一页  
170 - if (dataList.size() < pageSize) {  
171 - break;  
172 - }  
173 - }  
174 - } catch (IOException e) {  
175 - log.error(e.getMessage());  
176 - } finally {  
177 - // 无论导入成功与否,都清理Redis临时数据  
178 - deleteRedisTempData(batchId, templateName);  
179 - }  
180 -  
181 - return totalImported;  
182 - }  
183 - /**  
184 - * @Author wangqian  
185 - * @Description 保存数据  
186 - * @Date 2025/7/20 11:30  
187 - * @Param template  
188 - * @param dataList  
189 - * @param operator  
190 - * @Return void  
191 - */  
192 - private void insertData(TemplateEnum template, List<?> dataList, String operator) {  
193 - if (CollectionUtils.isEmpty(dataList)) {  
194 - log.info("数据列表为空,跳过插入操作");  
195 - return;  
196 - }  
197 - //单位集合  
198 - List<DeptRespDTO> companyList = deptApi.getSubDeptList();  
199 - // 1. 预加载部门映射(循环外加载,避免重复查询)  
200 - List<Long> deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList());  
201 - Map<String, Long> nameToCompanyIdMap = CollectionUtils.isEmpty(companyList) ? Map.of()  
202 - : companyList.stream()  
203 - // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)  
204 - .collect(Collectors.toMap(  
205 - dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)  
206 - DeptRespDTO::getId, // key 映射:取 companyId  
207 - (oldValue, newValue) -> newValue  
208 - ));  
209 -  
210 -  
211 - // 模板类型校验  
212 - Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch  
213 - // 根据不同模板类型执行不同的处理逻辑  
214 - if (WaterFeeImport.class.isAssignableFrom(importClass)) {  
215 - Map<Long, String> companyIdToNamedMap = CollectionUtils.isEmpty(companyList) ? Map.of()  
216 - : companyList.stream()  
217 - // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)  
218 - .collect(Collectors.toMap(  
219 - DeptRespDTO::getId, // key 映射:取 companyId  
220 - dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)  
221 - (oldValue, newValue) -> newValue  
222 - ));  
223 -  
224 - // 处理水费数据  
225 - List<WaterFeeDO> feeList = ((List<WaterFeeImport>) dataList).stream()  
226 - .map(importObj -> {  
227 - WaterFeeDO fee = new WaterFeeDO();  
228 - BeanUtils.copyProperties(importObj, fee);  
229 - fee.setCompanyId(String.valueOf(getCompanyId()));  
230 - if (getCompanyId() == 100) {  
231 - fee.setCompanyName("蓟城山水");  
232 - } else {  
233 - // 设置单位id  
234 - fee.setCompanyName(String.valueOf(companyIdToNamedMap.get(fee.getCompanyId())));  
235 - }  
236 - return fee;  
237 - })  
238 - .collect(Collectors.toMap(  
239 - // 生成唯一键:feeMonth + 分隔符 + meterCode(避免不同组合拼接后重复)  
240 - fee -> fee.getFeeMonth() + "_" + fee.getMeterCode(),  
241 - // 值为对象本身  
242 - Function.identity(),  
243 - // 当键重复时,保留第一个出现的对象(可根据需求改为保留最后一个:(existing, replacement) -> replacement)  
244 - (existing, replacement) -> replacement  
245 - ))  
246 - .values()  
247 - .stream()  
248 - .collect(Collectors.toList());  
249 -  
250 - if (!CollectionUtils.isEmpty(feeList)) {  
251 - //查询重复项,进行删除  
252 - List<Long> ids = waterFeeMapper.selectDuplicateIds(feeList);  
253 - if (!CollectionUtils.isEmpty(ids)) {  
254 - waterFeeMapper.deleteByIds(ids);  
255 - }  
256 - // 调用批量插入方法  
257 - waterFeeMapper.insertBatch(feeList);  
258 - }  
259 - log.info("成功插入 {} 条水费数据", feeList.size());  
260 - } else if (PersonalCostImport.class.isAssignableFrom(importClass)) {  
261 - //去重  
262 - dataList = personalFeeReplacement((List<PersonalCostImport>) dataList);  
263 - //获取班组对象  
264 - Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);  
265 -  
266 - // 3. 转换导入数据为PersonalCost列表(Stream优化+空指针防护)  
267 - List<PersonalCostImport> feeList = (List<PersonalCostImport>)dataList;  
268 - List<PersonalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());  
269 - if (!CollectionUtils.isEmpty(feeList)) {  
270 - for (PersonalCostImport importObj : feeList) {  
271 - PersonalCostDO fee = new PersonalCostDO();  
272 - BeanUtils.copyProperties(importObj, fee);  
273 - // 设置单位id  
274 - fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));  
275 - //设置班组id  
276 - fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));  
277 - saveFeeList.add(fee);  
278 - }  
279 - }  
280 - // 4. 处理重复数据(删除+插入原子性)  
281 - if (!feeList.isEmpty()) {  
282 - // 4.1 查询并删除重复项  
283 -  
284 -// List<Long> duplicateIds = personalCostMapper.selectDuplicateIds(saveFeeList);  
285 -// if (!duplicateIds.isEmpty()) {  
286 -// personalCostMapper.deleteByIds(duplicateIds); 1 +//package com.zteits.urbanops.module.garden.service.costfee;
  2 +//
  3 +//import com.fasterxml.jackson.core.JsonProcessingException;
  4 +//import com.fasterxml.jackson.databind.ObjectMapper;
  5 +//import com.zteits.urbanops.framework.security.core.LoginUser;
  6 +//import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController;
  7 +//import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.*;
  8 +//import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO;
  9 +//import com.zteits.urbanops.module.garden.dal.dataobject.mechanicalcost.MechanicalCostDO;
  10 +//import com.zteits.urbanops.module.garden.dal.dataobject.personalcost.PersonalCostDO;
  11 +//import com.zteits.urbanops.module.garden.dal.dataobject.rentalmechanicalcost.RentalMechanicalCostDO;
  12 +//import com.zteits.urbanops.module.garden.dal.dataobject.wasterecord.WasteRecordDO;
  13 +//import com.zteits.urbanops.module.garden.dal.dataobject.waterfee.WaterFeeDO;
  14 +//import com.zteits.urbanops.module.garden.dal.mysql.departmentcost.DepartmentCostMapper;
  15 +//import com.zteits.urbanops.module.garden.dal.mysql.mechanicalcost.MechanicalCostMapper;
  16 +//import com.zteits.urbanops.module.garden.dal.mysql.personalcost.PersonalCostMapper;
  17 +//import com.zteits.urbanops.module.garden.dal.mysql.rentalmechanicalcost.RentalMechanicalCostMapper;
  18 +//import com.zteits.urbanops.module.garden.dal.mysql.wasterecord.WasteRecordMapper;
  19 +//import com.zteits.urbanops.module.garden.dal.mysql.waterfee.WaterFeeMapper;
  20 +//import com.zteits.urbanops.module.garden.enums.AssignTasksConstants;
  21 +//import com.zteits.urbanops.module.garden.enums.CostFeeConstants;
  22 +//import com.zteits.urbanops.module.garden.enums.TemplateEnum;
  23 +//import com.zteits.urbanops.module.system.api.dept.DeptApi;
  24 +//import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO;
  25 +//import jakarta.annotation.Resource;
  26 +//import jakarta.validation.ConstraintViolation;
  27 +//import jakarta.validation.ConstraintViolationException;
  28 +//import jakarta.validation.Validator;
  29 +//import lombok.extern.slf4j.Slf4j;
  30 +//import org.apache.commons.lang3.StringUtils;
  31 +//import org.springframework.beans.BeanUtils;
  32 +//import org.springframework.beans.factory.annotation.Autowired;
  33 +//import org.springframework.data.redis.core.BoundHashOperations;
  34 +//import org.springframework.data.redis.core.RedisTemplate;
  35 +//import org.springframework.stereotype.Service;
  36 +//import org.springframework.transaction.annotation.Transactional;
  37 +//import org.springframework.util.CollectionUtils;
  38 +//
  39 +//import java.io.IOException;
  40 +//import java.time.LocalDate;
  41 +//import java.util.*;
  42 +//import java.util.concurrent.TimeUnit;
  43 +//import java.util.function.Function;
  44 +//import java.util.stream.Collectors;
  45 +//import java.util.stream.IntStream;
  46 +//
  47 +//import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
  48 +//import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception;
  49 +//import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception0;
  50 +//import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.*;
  51 +//import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.COST_FEE_NOT_EXISTS_001;
  52 +//
  53 +///**
  54 +// * @Classname CostFeeService
  55 +// * @Description 模板导入公共实现
  56 +// * @Date 2025/7/20 15:12
  57 +// * @Created by wangqian
  58 +// */
  59 +//@Service
  60 +//@Slf4j
  61 +//public class CostFeeServiceImpl implements CostFeeService {
  62 +//
  63 +// // Redis相关配置
  64 +// private static final String REDIS_KEY_PREFIX = "excel:import:";
  65 +// private static final long EXPIRE_TIME = 30; // 预览数据过期时间:30分钟
  66 +// private static final TimeUnit EXPIRE_UNIT = TimeUnit.MINUTES;
  67 +// @Autowired
  68 +// private RedisTemplate<String, String> redisTemplate;
  69 +// @Autowired
  70 +// private ObjectMapper objectMapper;
  71 +// @Resource
  72 +// private Validator validator;
  73 +// @Resource
  74 +// private DeptApi deptApi;
  75 +//
  76 +// @Autowired
  77 +// private WaterFeeMapper waterFeeMapper;
  78 +// @Autowired
  79 +// private PersonalCostMapper personalCostMapper;
  80 +// @Autowired
  81 +// private DepartmentCostMapper departmentCostMapper;
  82 +// @Autowired
  83 +// private MechanicalCostMapper mechanicalCostMapper;
  84 +// @Autowired
  85 +// private RentalMechanicalCostMapper rentalMechanicalCostMapper;
  86 +// @Autowired
  87 +// private WasteRecordMapper wasteRecordMapper;
  88 +//
  89 +//
  90 +// @Override
  91 +// /**
  92 +// * 保存数据到Redis
  93 +// */
  94 +// public String saveDataToRedis(List<?> dataList, String templateName){
  95 +// String batchId = UUID.randomUUID().toString().replace("-", "");
  96 +// String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;
  97 +//
  98 +// // 存储总记录数
  99 +// redisTemplate.opsForValue().set(
  100 +// redisKey + ":total",
  101 +// String.valueOf(dataList.size()),
  102 +// EXPIRE_TIME,
  103 +// EXPIRE_UNIT
  104 +// );
  105 +//
  106 +// // 使用Hash存储每条数据(key:索引,value:JSON字符串)
  107 +// BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");
  108 +// try {
  109 +// for (int i = 0; i < dataList.size(); i++) {
  110 +// String jsonData = objectMapper.writeValueAsString(dataList.get(i));
  111 +// hashOps.put(String.valueOf(i), jsonData);
  112 +// }
  113 +// } catch (JsonProcessingException e) {
  114 +// log.error(e.getMessage());
  115 +// }
  116 +// // 设置Hash过期时间
  117 +// hashOps.expire(EXPIRE_TIME, EXPIRE_UNIT);
  118 +//
  119 +// log.info("数据保存到Redis成功,批次ID:{},记录数:{}", batchId, dataList.size());
  120 +// return batchId;
  121 +// }
  122 +//
  123 +// @Override
  124 +// @Transactional
  125 +// public int submitImport(String batchId, String templateName, String operator){
  126 +// // 模板类型校验
  127 +// TemplateEnum template = TemplateEnum.getByCode(templateName);
  128 +// Class<?> importClass = template.getImportClass();
  129 +// if (importClass == null) {
  130 +// throw exception0(BAD_REQUEST.getCode(),"导入模板配置异常");
  131 +// }
  132 +//
  133 +// // 分页参数设置
  134 +// int pageNum = 1;
  135 +// final int pageSize = 100000; // 每页处理条数,可根据实际情况调整
  136 +// int totalImported = 0; // 总导入数量累计
  137 +//
  138 +// try {
  139 +// while (true) {
  140 +// // 分页从Redis获取数据
  141 +// CostFeeController.PageResult<?> pageResult = getPagedDataFromRedis(
  142 +// batchId, templateName, pageNum, pageSize, importClass);
  143 +// List<?> dataList = pageResult.getData();
  144 +//
  145 +// // 如果当前页无数据,说明已处理完毕
  146 +// if (dataList == null || dataList.isEmpty()) {
  147 +// // 首次分页就无数据,抛出异常
  148 +// if (pageNum == 1) {
  149 +// throw exception0(BAD_REQUEST.getCode(),"没有可导入的数据或数据已过期");
  150 +// }
  151 +// break;
287 // } 152 // }
288 -  
289 - // 4.2 批量插入主表  
290 - personalCostMapper.insertBatch(saveFeeList);  
291 - }  
292 - log.info("成功插入 {} 人员费用数据", dataList.size());  
293 - } else if (DepartmentCostImport.class.isAssignableFrom(importClass)) {  
294 - //去重  
295 - dataList = deptFeeReplacement((List<DepartmentCostImport>)dataList);  
296 - List<DepartmentCostDO> feeList = ((List<DepartmentCostImport>) dataList).stream()  
297 - .map(importObj -> {  
298 - DepartmentCostDO fee = new DepartmentCostDO();  
299 - BeanUtils.copyProperties(importObj, fee);  
300 - // 设置单位id  
301 - fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));  
302 - return fee;  
303 - })  
304 - .collect(Collectors.toList());  
305 - if (!CollectionUtils.isEmpty(feeList)) {  
306 - //查询重复项,进行删除  
307 - List<Long> ids = departmentCostMapper.selectDuplicateIds(feeList);  
308 - if (!CollectionUtils.isEmpty(ids)) {  
309 - departmentCostMapper.deleteByIds(ids);  
310 - }  
311 - // 调用批量插入方法  
312 - departmentCostMapper.insertBatch(feeList);  
313 - }  
314 - log.info("成功插入 {} 综合费用数据", feeList.size());  
315 - } else if (MechanicalCostImport.class.isAssignableFrom(importClass)) {  
316 - List<MechanicalCostImport> feeList = (List<MechanicalCostImport>) dataList;  
317 - List<MechanicalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());  
318 - if (!CollectionUtils.isEmpty(feeList)) {  
319 - //获取班组对象  
320 - Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);  
321 - for (MechanicalCostImport importObj : feeList) {  
322 - MechanicalCostDO fee = new MechanicalCostDO();  
323 - BeanUtils.copyProperties(importObj, fee);  
324 - // 设置单位id  
325 - fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));  
326 - //设置班组id  
327 - fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));  
328 - // 3.3 计算结束时间(空指针防护)  
329 - Optional.ofNullable(fee.getStartUseTime())  
330 - .ifPresent(startTime -> {  
331 - int useDuration = Optional.ofNullable(fee.getServiceLife())  
332 - .map(Math::toIntExact) // 处理可能的溢出  
333 - .orElse(0);  
334 - fee.setEndUseTime(this.getEndLocalDateForYears(startTime, useDuration));  
335 - });  
336 -  
337 - saveFeeList.add(fee);  
338 - }  
339 - }  
340 - // 4. 批量插入主表(空集合直接跳过)  
341 - if (!feeList.isEmpty()) {  
342 - //查询重复项,进行删除  
343 - List<Long> ids = mechanicalCostMapper.selectDuplicateIds(saveFeeList);  
344 - if (!CollectionUtils.isEmpty(ids)) {  
345 - mechanicalCostMapper.deleteByIds(ids);  
346 - }  
347 - mechanicalCostMapper.insertBatch(saveFeeList);  
348 - }  
349 - log.info("成功插入 {} 自有机械费用数据", dataList.size());  
350 - } else if (RentalMechanicalCostImport.class.isAssignableFrom(importClass)) {  
351 - List<RentalMechanicalCostImport> feeList = (List<RentalMechanicalCostImport>)dataList;  
352 - List<RentalMechanicalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());  
353 - if (!CollectionUtils.isEmpty(feeList)) {  
354 - //获取班组对象  
355 - Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);  
356 - for (RentalMechanicalCostImport importObj : feeList) {  
357 - RentalMechanicalCostDO fee = new RentalMechanicalCostDO();  
358 - BeanUtils.copyProperties(importObj, fee);  
359 - // 设置单位id  
360 - fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));  
361 - //设置班组id  
362 - List<TeamBizdomainRelVo> teamRels = new ArrayList<>();  
363 - String teamIdsStr = StringUtils.defaultString(importObj.getTeamIds()).replace(",", ",");  
364 - String weightsStr = StringUtils.defaultString(importObj.getWeight()).replace(",", ",");  
365 -  
366 - String[] teamIds = teamIdsStr.split(",");  
367 - String[] weights = weightsStr.split(",");  
368 - for (int i = 0; i < teamIds.length; i++) {  
369 - TeamBizdomainRelVo rel = new TeamBizdomainRelVo();  
370 - rel.setTeamName(teamIds[i].trim());  
371 - // 提前处理部门ID映射(复用deptMap)  
372 - rel.setTeamId(Optional.ofNullable(teamIdMap.get(teamIds[i].trim())).map(String::valueOf).orElse(null));  
373 - rel.setWeight(Long.valueOf(weights[i].trim()));  
374 - teamRels.add(rel);  
375 - }  
376 - fee.setTeamIds(teamRels);  
377 -  
378 - // 3.3 计算结束时间(空指针防护)  
379 - Optional.ofNullable(fee.getStartUseTime())  
380 - .ifPresent(startTime -> {  
381 - int useDuration = Optional.ofNullable(fee.getUseDuration())  
382 - .map(Math::toIntExact) // 处理可能的溢出  
383 - .orElse(0);  
384 - fee.setEndUseTime(getEndLocalDateForYears(startTime, useDuration));  
385 - });  
386 -  
387 - saveFeeList.add(fee);  
388 - }  
389 - }  
390 - if (!feeList.isEmpty()) {  
391 - rentalMechanicalCostMapper.insertBatch(saveFeeList);  
392 - }  
393 - log.info("成功插入 {} 租赁机械费用数据", dataList.size());  
394 - } else if (GardenWasteRecordImport.class.isAssignableFrom(importClass)) {  
395 - //获取班组对象  
396 - List<GardenWasteRecordImport> feeList = (List<GardenWasteRecordImport>)dataList;  
397 - List<WasteRecordDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());  
398 - if (!CollectionUtils.isEmpty(feeList)) {  
399 - Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);  
400 - for (GardenWasteRecordImport importObj : feeList) {  
401 - WasteRecordDO fee = new WasteRecordDO();  
402 - BeanUtils.copyProperties(importObj, fee);  
403 - // 设置单位id  
404 - fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));  
405 - //设置班组id  
406 - fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));  
407 -  
408 - saveFeeList.add(fee);  
409 - }  
410 - }  
411 - // 4. 批量插入主表(若主表插入失败,事务回滚)  
412 - if (!feeList.isEmpty()) {  
413 - wasteRecordMapper.insertBatch(saveFeeList);  
414 - }  
415 -  
416 - log.info("成功插入 {} 园林废弃物费用数据", dataList.size());  
417 - }  
418 -  
419 - }  
420 -  
421 - /**  
422 - * 从Redis获取分页数据  
423 - */  
424 - @Override  
425 - public <T > CostFeeController.PageResult < T > getPagedDataFromRedis(String batchId, String templateName,  
426 - int page, int size, Class<T > clazz) throws IOException {  
427 - String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;  
428 -  
429 - // 获取总记录数  
430 - String totalStr = redisTemplate.opsForValue().get(redisKey + ":total");  
431 - int total = totalStr != null ? Integer.parseInt(totalStr) : 0;  
432 -  
433 - // 计算分页范围  
434 - int start = (page - 1) * size;  
435 - int end = Math.min(start + size - 1, total - 1);  
436 -  
437 - // 分页查询数据  
438 - List<T> pageData = new ArrayList<>();  
439 - if (start <= end && total > 0) {  
440 - BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");  
441 -  
442 - // 批量获取指定范围的索引对应的数据  
443 - List<String> indexes = IntStream.rangeClosed(start, end)  
444 - .mapToObj(String::valueOf)  
445 - .collect(Collectors.toList());  
446 - List<String> jsonList = hashOps.multiGet(indexes);  
447 -  
448 - // 反序列化为对象  
449 - for (String json : jsonList) {  
450 - if (json != null) {  
451 - pageData.add(objectMapper.readValue(json, clazz));  
452 - }  
453 - }  
454 - }  
455 -  
456 - return new CostFeeController.PageResult<>(page, size, total, pageData);  
457 - }  
458 -  
459 -  
460 - /**  
461 - * 从Redis获取全部数据  
462 - */  
463 - private <T > List < T > getAllDataFromRedis(String batchId, String templateName, Class < T > clazz) throws  
464 - IOException {  
465 - String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;  
466 - BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");  
467 -  
468 - // 获取所有数据的JSON字符串  
469 - List<String> jsonList = hashOps.values();  
470 - List<T> dataList = new ArrayList<>(jsonList.size());  
471 -  
472 - // 反序列化为对象  
473 - for (String json : jsonList) {  
474 - if (json != null) {  
475 - dataList.add(objectMapper.readValue(json, clazz));  
476 - }  
477 - }  
478 -  
479 - return dataList;  
480 - }  
481 -  
482 -  
483 - /**  
484 - * 删除Redis临时数据  
485 - */  
486 - private void deleteRedisTempData (String batchId, String templateName){  
487 - String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;  
488 - redisTemplate.delete(redisKey + ":total");  
489 - redisTemplate.delete(redisKey + ":items");  
490 - log.info("Redis临时数据已删除,批次ID:{}", batchId);  
491 - }  
492 - /**  
493 - * @Author wangqian  
494 - * @Description 导入内容校验  
495 - * @Date 2025/11/24 8:40  
496 - * @Param dataList  
497 - * @param template  
498 - * @Return void  
499 - */  
500 - public void validateImportData(List dataList, TemplateEnum template) {  
501 - if (CollectionUtils.isEmpty(dataList)) {  
502 - return; // 空列表无需校验  
503 - }  
504 - //登录用户单位  
505 - Long belongCompanyId = getCompanyId();  
506 - //单位集合  
507 - List<DeptRespDTO> companyList = deptApi.getSubDeptList();  
508 - List<Long> deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList());  
509 - Map<String, Long> nameToCompanyIdMap = CollectionUtils.isEmpty(companyList)  
510 - ? Map.of() // 空列表返回空不可变 Map(避免 null)  
511 - : companyList.stream()  
512 - // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)  
513 - .collect(Collectors.toMap(  
514 - dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)  
515 - DeptRespDTO::getId, // key 映射:取 companyId  
516 - (oldValue, newValue) -> newValue  
517 - ));  
518 - //班组集合  
519 - Map<Long, List<String>> deptList = this.getDetpList(deptIds);  
520 - // 模板类型校验  
521 - Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch  
522 - // 根据不同模板类型执行不同的处理逻辑  
523 - if (WaterFeeImport.class.isAssignableFrom(importClass)) {  
524 -  
525 - List<WaterFeeImport> importList = (List<WaterFeeImport>) dataList;  
526 -  
527 - // 2. 遍历,逐个创建  
528 - for (int i = 0; i < importList.size(); i++) {  
529 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
530 - WaterFeeImport importBean = importList.get(i);  
531 -  
532 - try {  
533 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
534 - Set<ConstraintViolation<WaterFeeImport>> violations = validator.validate(importBean);  
535 - if (!violations.isEmpty()) {  
536 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
537 - // StringBuilder errorMsg = new StringBuilder();  
538 - for (ConstraintViolation<WaterFeeImport> violation : violations) {  
539 - //errorMsg.append(violation.getMessage()).append(";");  
540 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
541 - "第[{}]条数据:"+ violation.getMessage()  
542 - ), rowNum);  
543 - }  
544 - // 抛出包含行号和所有错误的异常  
545 - //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);  
546 - }  
547 -  
548 - } catch (ConstraintViolationException ex) {  
549 - // 5. 包装异常,明确行号 + 真实校验错误(  
550 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
551 - }  
552 - }  
553 - }  
554 - else if (PersonalCostImport.class.isAssignableFrom(importClass)) {  
555 -  
556 - List<PersonalCostImport> importList = (List<PersonalCostImport>) dataList;  
557 - // 2. 遍历,逐个创建  
558 - for (int i = 0; i < importList.size(); i++) {  
559 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
560 - PersonalCostImport importBean = importList.get(i);  
561 -  
562 - try {  
563 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
564 - Set<ConstraintViolation<PersonalCostImport>> violations = validator.validate(importBean);  
565 - if (!violations.isEmpty()) {  
566 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
567 - //StringBuilder errorMsg = new StringBuilder();  
568 - for (ConstraintViolation<PersonalCostImport> violation : violations) {  
569 - // errorMsg.append(violation.getMessage()).append(";");  
570 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
571 - "第[{}]条数据:"+ violation.getMessage()  
572 - ), rowNum);  
573 - }  
574 - // 抛出包含行号和所有错误的异常  
575 - // throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);  
576 - }  
577 - Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());  
578 - //单位校验  
579 - validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);  
580 - //部门名称校验  
581 - validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());  
582 -  
583 - } catch (ConstraintViolationException ex) {  
584 - // 5. 包装异常,明确行号 + 真实校验错误(  
585 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
586 - }  
587 - }  
588 - }  
589 - else if (GardenWasteRecordImport.class.isAssignableFrom(importClass)) {  
590 -  
591 - List<GardenWasteRecordImport> importList = (List<GardenWasteRecordImport>) dataList;  
592 - // 2. 遍历,逐个创建  
593 - for (int i = 0; i < importList.size(); i++) {  
594 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
595 - GardenWasteRecordImport importBean = importList.get(i);  
596 -  
597 - try {  
598 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
599 - Set<ConstraintViolation<GardenWasteRecordImport>> violations = validator.validate(importBean);  
600 - if (!violations.isEmpty()) {  
601 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
602 - //StringBuilder errorMsg = new StringBuilder();  
603 - for (ConstraintViolation<GardenWasteRecordImport> violation : violations) {  
604 - //errorMsg.append(violation.getMessage()).append(";");  
605 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
606 - "第[{}]条数据:"+ violation.getMessage()  
607 - ), rowNum);  
608 - }  
609 - // 抛出包含行号和所有错误的异常  
610 - //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);  
611 - }  
612 - Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());  
613 - //单位校验  
614 - validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);  
615 - //部门名称校验  
616 - validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());  
617 -  
618 - } catch (ConstraintViolationException ex) {  
619 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
620 - }  
621 - }  
622 - }  
623 - else if (MechanicalCostImport.class.isAssignableFrom(importClass)) {  
624 -  
625 - List<MechanicalCostImport> importList = (List<MechanicalCostImport>) dataList;  
626 - // 2. 遍历,逐个创建  
627 - for (int i = 0; i < importList.size(); i++) {  
628 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
629 - MechanicalCostImport importBean = importList.get(i);  
630 -  
631 - try {  
632 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
633 - Set<ConstraintViolation<MechanicalCostImport>> violations = validator.validate(importBean);  
634 - if (!violations.isEmpty()) {  
635 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
636 - //StringBuilder errorMsg = new StringBuilder();  
637 - for (ConstraintViolation<MechanicalCostImport> violation : violations) {  
638 - //errorMsg.append(violation.getMessage()).append(";");  
639 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
640 - "第[{}]条数据:"+ violation.getMessage()  
641 - ), rowNum);  
642 - }  
643 - // 抛出包含行号和所有错误的异常  
644 - //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);  
645 - }  
646 - Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());  
647 - //单位校验  
648 - validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);  
649 - //部门名称校验  
650 - validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());  
651 -  
652 - //车辆校验  
653 - if (CostFeeConstants.MECH_TYPE.equals(importBean.getMechType())) {  
654 - if (StringUtils.isEmpty(importBean.getLicensePlate())) {  
655 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
656 - "第[{}]条数据:车牌号不能为空"  
657 - ), rowNum);  
658 - }  
659 - if (StringUtils.isEmpty(importBean.getVehicleType())) {  
660 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
661 - "第[{}]条数据:车辆类型不能为空"  
662 - ), rowNum);  
663 -  
664 - }  
665 - if (StringUtils.isEmpty(importBean.getVehicleCategory())) {  
666 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
667 - "第%d条数据:车辆类别不能为空"  
668 - ), rowNum);  
669 - }  
670 - }  
671 -  
672 - } catch (ConstraintViolationException ex) {  
673 - // 5. 包装异常,明确行号 + 真实校验错误(  
674 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
675 - }  
676 - }  
677 - }  
678 - else if (RentalMechanicalCostImport.class.isAssignableFrom(importClass)) {  
679 -  
680 - List<RentalMechanicalCostImport> importList = (List<RentalMechanicalCostImport>) dataList;  
681 - // 2. 遍历,逐个创建  
682 - for (int i = 0; i < importList.size(); i++) {  
683 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
684 - RentalMechanicalCostImport importBean = importList.get(i);  
685 -  
686 - try {  
687 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
688 - Set<ConstraintViolation<RentalMechanicalCostImport>> violations = validator.validate(importBean);  
689 - if (!violations.isEmpty()) {  
690 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
691 - // StringBuilder errorMsg = new StringBuilder();  
692 - for (ConstraintViolation<RentalMechanicalCostImport> violation : violations) {  
693 - // errorMsg.append(violation.getMessage()).append(";");  
694 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
695 - "第[{}]条数据:"+violation.getMessage()  
696 - ), rowNum);  
697 - }  
698 - // 抛出包含行号和所有错误的异常  
699 - //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);  
700 - }  
701 - // 处理可能的空值  
702 - String teamIdsStr = StringUtils.defaultString(importBean.getTeamIds());  
703 - String weightsStr = StringUtils.defaultString(importBean.getWeight());  
704 -  
705 - // 统一替换中文逗号并分割  
706 - String[] teamIds = teamIdsStr.replace(",", ",").split(",");  
707 - String[] weights = weightsStr.replace(",", ",").split(",");  
708 -  
709 - // 校验数组长度是否一致  
710 - if (teamIds.length != weights.length) {  
711 - throw exception0(BAD_REQUEST.getCode(), String.format(  
712 - "第%d条数据:权重值和所选班组数量不符(班组:%d个,权重:%d个)",  
713 - rowNum, teamIds.length, weights.length  
714 - ));  
715 - }  
716 - Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());  
717 - //单位校验  
718 - validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);  
719 - //部门名称校验  
720 - validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());  
721 -  
722 - } catch (ConstraintViolationException ex) {  
723 - // 5. 包装异常,明确行号 + 真实校验错误(  
724 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
725 - }  
726 - }  
727 - }  
728 - else if (DepartmentCostImport.class.isAssignableFrom(importClass)) {  
729 -  
730 - List<DepartmentCostImport> importList = (List<DepartmentCostImport>) dataList;  
731 - // 2. 遍历,逐个创建  
732 - for (int i = 0; i < importList.size(); i++) {  
733 - int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)  
734 - DepartmentCostImport importBean = importList.get(i);  
735 -  
736 - try {  
737 - // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)  
738 - Set<ConstraintViolation<DepartmentCostImport>> violations = validator.validate(importBean);  
739 - if (!violations.isEmpty()) {  
740 - // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)  
741 - //StringBuilder errorMsg = new StringBuilder();  
742 - for (ConstraintViolation<DepartmentCostImport> violation : violations) {  
743 - //errorMsg.append(violation.getMessage()).append(";");  
744 - throw exception0(BAD_REQUEST.getCode() ,String.format(  
745 - "第[{}]条数据:"+violation.getMessage()  
746 - ), rowNum);  
747 - }  
748 - // 抛出包含行号和所有错误的异常  
749 - //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);  
750 - }  
751 -  
752 - Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());  
753 - //单位校验  
754 - validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);  
755 - } catch (ConstraintViolationException ex) {  
756 - // 5. 包装异常,明确行号 + 真实校验错误(  
757 - throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);  
758 - }  
759 - }  
760 - }  
761 - }  
762 -  
763 - private void validatecompany(int rowNum, String companyName, Long companyId, Long belongCompanyId){  
764 - // 校验数组长度是否一致  
765 - if (companyId == null) {  
766 - throw exception0(BAD_REQUEST.getCode(), String.format(  
767 - "第%d条数据:单位名称不正确(%s)",  
768 - rowNum, companyName  
769 - ));  
770 - }  
771 - // 校验导入单位  
772 - if (belongCompanyId != 100 && !belongCompanyId.equals(companyId)) {  
773 - throw exception0(BAD_REQUEST.getCode(), String.format(  
774 - "第%d条数据:导入单位%s与当前操作员单位不一致",  
775 - rowNum, companyName  
776 - ));  
777 - }  
778 - }  
779 - private void validateTeams(int rowNum, List<String> dept, String teamIds) {  
780 - if (StringUtils.isNotEmpty(teamIds)) {  
781 - String[] teamNames = teamIds.split(",");  
782 - Set<String> checkedTeams = new HashSet<>(); // 用于存储已校验的团队名称,检测重复  
783 -  
784 - for (String teamName : teamNames) {  
785 - String trimmedTeamName = teamName.trim(); // 处理前后空格  
786 -  
787 - // 重复校验:如果Set中已存在该团队名称,说明重复  
788 - if (checkedTeams.contains(trimmedTeamName)) {  
789 - throw exception0(BAD_REQUEST.getCode(), String.format(  
790 - "第%d条数据:%s名称重复",  
791 - rowNum, teamName  
792 - ));  
793 - }  
794 -  
795 - // 存在性校验:检查团队是否在dept中存在  
796 - if (!dept.contains(trimmedTeamName)) {  
797 - throw exception0(BAD_REQUEST.getCode(), String.format(  
798 - "第%d条数据:%s名称不正确",  
799 - rowNum, teamName  
800 - ));  
801 - }  
802 -  
803 - // 将校验通过的团队名称加入Set,用于后续重复判断  
804 - checkedTeams.add(trimmedTeamName);  
805 - }  
806 - }  
807 - }  
808 -  
809 - //查询单位下的班组信息  
810 - private Map<Long,List<String>> getDetpList (List<Long> deptIds) {  
811 - Map<Long,List<String>> listMap = new HashMap<>();  
812 - for (Long depId : deptIds) {  
813 - List<DeptRespDTO> deptList = deptApi.getChildDeptList(depId);  
814 - List<String> names = deptList.stream().map(DeptRespDTO::getName).collect(Collectors.toList());  
815 - listMap.put(depId,names);  
816 - }  
817 - return listMap;  
818 - }  
819 -  
820 - /**  
821 - * 按salaryMonth、expenseName、companyId三个字段去重  
822 - * @param costList 原始部门成本列表  
823 - * @return 去重后的列表  
824 - */  
825 - public List<DepartmentCostImport> deptFeeReplacement(List<DepartmentCostImport> costList) {  
826 - if (costList == null || costList.isEmpty()) {  
827 - return new ArrayList<>();  
828 - }  
829 -  
830 - // 1. 以三个字段的组合作为键(使用分隔符避免不同组合产生相同键)  
831 - // 2. 遇到重复键时保留第一条记录  
832 - Map<String, DepartmentCostImport> uniqueMap = costList.stream()  
833 - .collect(Collectors.toMap(  
834 - // 生成唯一键:salaryMonth|expenseName|companyId  
835 - cost -> buildUniqueKey(cost),  
836 - // 键不存在时存入当前对象  
837 - cost -> cost,  
838 - // 键已存在时保留最后一个  
839 - (existing, replacement) -> replacement  
840 - ));  
841 -  
842 - // 将去重后的结果转为List返回  
843 - return new ArrayList<>(uniqueMap.values());  
844 - }  
845 -  
846 - /**  
847 - * 构建三个字段的唯一组合键(处理null值)  
848 - */  
849 - private String buildUniqueKey(DepartmentCostImport cost) {  
850 - // 使用Objects.toString处理可能的null值,避免NullPointerException  
851 - return Objects.toString(cost.getSalaryMonth(), "") + "|" +  
852 - Objects.toString(cost.getExpenseName(), "") + "|" +  
853 - Objects.toString(cost.getCompanyName(), "");  
854 - }  
855 -  
856 - /**  
857 - * 按salaryMonth、name、companyId、teamIdList四个字段去重  
858 - * @param costList 原始个人成本列表  
859 - * @return 去重后的列表  
860 - */  
861 - public List<PersonalCostImport> personalFeeReplacement(List<PersonalCostImport> costList) {  
862 - if (costList == null || costList.isEmpty()) {  
863 - return new ArrayList<>();  
864 - }  
865 -  
866 - // 1. 以四个字段的组合作为键(特别处理集合类型的teamIdList)  
867 - // 2. 遇到重复键时保留第一条记录  
868 - Map<String, PersonalCostImport> uniqueMap = costList.stream()  
869 - .collect(Collectors.toMap(  
870 - // 生成唯一键:salaryMonth|name|companyId|teamIdListStr  
871 - this::buildUniqueKey,  
872 - // 键不存在时存入当前对象  
873 - cost -> cost,  
874 - // 键已存在时保留第一个对象(可改为replacement保留最后一个)  
875 - (existing, replacement) -> replacement  
876 - ));  
877 -  
878 - // 将去重后的结果转为List返回  
879 - return new ArrayList<>(uniqueMap.values());  
880 - }  
881 -  
882 - /**  
883 - * 构建四个字段的唯一组合键(处理null值和集合类型)  
884 - */  
885 - private String buildUniqueKey(PersonalCostImport cost) {  
886 - // 处理普通字段的null值  
887 - String salaryMonth = Objects.toString(cost.getSalaryMonth(), "");  
888 - String name = Objects.toString(cost.getName(), "");  
889 - String companyId = Objects.toString(cost.getCompanyName(), "");  
890 - String teamIds = Objects.toString(cost.getTeamIds(), "");  
891 - // 拼接唯一键  
892 - return salaryMonth + "|" + name + "|" + companyId + "|" + teamIds;  
893 - }  
894 -  
895 -  
896 - public static LocalDate getEndLocalDateForYears(LocalDate startDate, int years) {  
897 - if (startDate == null) {  
898 - throw new IllegalArgumentException("开始日期不能为null");  
899 - }  
900 - if (years < 0) {  
901 - throw new IllegalArgumentException("年数不能为负数");  
902 - }  
903 - return startDate.plusYears(years);  
904 - }  
905 -  
906 - /**  
907 - * @Author wangqian  
908 - * @Description 根据班组名称获取班组id  
909 - * @Date 2025/11/26 19:38  
910 - */  
911 - /**  
912 - * 根据部门ID列表获取「部门名称→部门ID」的映射(含空值防护)  
913 - */  
914 - private Map<String, Long> getDeptListAll(List<Long> deptIds) {  
915 - Map<String, Long> deptNameMap = new HashMap<>();  
916 -  
917 - // 1. 空值安全:避免 deptIds 为 null/空列表导致 NPE  
918 - if (CollectionUtils.isEmpty(deptIds)) {  
919 - return deptNameMap;  
920 - }  
921 -  
922 - for (Long deptId : deptIds) {  
923 - // 2. 跳过 null 的部门ID  
924 - if (deptId == null) {  
925 - continue;  
926 - }  
927 -  
928 - // 3. 获取子部门列表(防护接口返回 null)  
929 - List<DeptRespDTO> deptList = deptApi.getChildDeptList(deptId);  
930 - if (CollectionUtils.isEmpty(deptList)) {  
931 - continue;  
932 - }  
933 -  
934 - // 4. 遍历子部门:过滤无效名称,避免 Map 出现 null/空字符串键  
935 - for (DeptRespDTO dept : deptList) {  
936 - if (dept == null) {  
937 - continue;  
938 - }  
939 - String deptName = dept.getName();  
940 - // 过滤 null/空白名称(避免 Map 键为 null 或空字符串)  
941 - if (deptName == null || deptName.trim().isEmpty()) {  
942 - continue;  
943 - }  
944 - // 若名称重复,后遍历的部门会覆盖先遍历的(可根据业务调整规则)  
945 - deptNameMap.put(deptName.trim(), dept.getId());  
946 - }  
947 - }  
948 -  
949 - return deptNameMap;  
950 - }  
951 - /**  
952 - * @Author wangqian  
953 - * @Description 根据班组名称获取班组id集合  
954 - * @Date 2025/11/26 20:07  
955 - */  
956 - private List<String> getDeptList(Map<String, Long> teamIdMap, String teamId) {  
957 - List<String> list = new ArrayList<>();  
958 -  
959 - // 1. 处理 teamId 为空的情况,直接返回空列表  
960 - if (StringUtils.isEmpty(teamId)) {  
961 - return list;  
962 - }  
963 -  
964 - // 2. 分割字符串,处理空元素  
965 - String[] teamArray = teamId.split(",");  
966 - for (String teamName : teamArray) {  
967 - // 3. 去除首尾空格,避免因空格导致匹配不到  
968 - String trimTeamName = teamName.trim();  
969 - if (StringUtils.isEmpty(trimTeamName)) {  
970 - continue; // 跳过空字符串元素  
971 - }  
972 -  
973 - // 4. 从Map中获取ID,处理null值(避免添加null到列表)  
974 - Long deptId = teamIdMap.get(trimTeamName);  
975 - if (deptId != null) {  
976 - list.add(String.valueOf(deptId)); // 直接添加Long类型,修正原String类型错误  
977 - }  
978 - }  
979 -  
980 - return list;  
981 - }  
982 -} 153 +//
  154 +// // 业务校验(每页单独校验)
  155 +// //validateBusinessData(dataList, template);
  156 +//
  157 +// // 调用业务服务执行本页数据导入
  158 +// int currentPageCount = dataList.size();
  159 +// insertData(template, dataList, operator);
  160 +// totalImported += currentPageCount;
  161 +//
  162 +// // 打印分页处理日志(可选)
  163 +// log.info("批次[{}]第{}页导入完成,本页{}条,累计{}条",
  164 +// batchId, pageNum, currentPageCount, totalImported);
  165 +//
  166 +// // 准备下一页
  167 +// pageNum++;
  168 +//
  169 +// // 如果当前页数据小于页大小,说明已到最后一页
  170 +// if (dataList.size() < pageSize) {
  171 +// break;
  172 +// }
  173 +// }
  174 +// } catch (IOException e) {
  175 +// log.error(e.getMessage());
  176 +// } finally {
  177 +// // 无论导入成功与否,都清理Redis临时数据
  178 +// deleteRedisTempData(batchId, templateName);
  179 +// }
  180 +//
  181 +// return totalImported;
  182 +// }
  183 +// /**
  184 +// * @Author wangqian
  185 +// * @Description 保存数据
  186 +// * @Date 2025/7/20 11:30
  187 +// * @Param template
  188 +// * @param dataList
  189 +// * @param operator
  190 +// * @Return void
  191 +// */
  192 +// private void insertData(TemplateEnum template, List<?> dataList, String operator) {
  193 +// if (CollectionUtils.isEmpty(dataList)) {
  194 +// log.info("数据列表为空,跳过插入操作");
  195 +// return;
  196 +// }
  197 +// //单位集合
  198 +// List<DeptRespDTO> companyList = deptApi.getSubDeptList();
  199 +// // 1. 预加载部门映射(循环外加载,避免重复查询)
  200 +// List<Long> deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList());
  201 +// Map<String, Long> nameToCompanyIdMap = CollectionUtils.isEmpty(companyList) ? Map.of()
  202 +// : companyList.stream()
  203 +// // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)
  204 +// .collect(Collectors.toMap(
  205 +// dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)
  206 +// DeptRespDTO::getId, // key 映射:取 companyId
  207 +// (oldValue, newValue) -> newValue
  208 +// ));
  209 +//
  210 +//
  211 +// // 模板类型校验
  212 +// Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch
  213 +// // 根据不同模板类型执行不同的处理逻辑
  214 +// if (WaterFeeImport.class.isAssignableFrom(importClass)) {
  215 +// Map<Long, String> companyIdToNamedMap = CollectionUtils.isEmpty(companyList) ? Map.of()
  216 +// : companyList.stream()
  217 +// // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)
  218 +// .collect(Collectors.toMap(
  219 +// DeptRespDTO::getId, // key 映射:取 companyId
  220 +// dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)
  221 +// (oldValue, newValue) -> newValue
  222 +// ));
  223 +//
  224 +// // 处理水费数据
  225 +// List<WaterFeeDO> feeList = ((List<WaterFeeImport>) dataList).stream()
  226 +// .map(importObj -> {
  227 +// WaterFeeDO fee = new WaterFeeDO();
  228 +// BeanUtils.copyProperties(importObj, fee);
  229 +// fee.setCompanyId(String.valueOf(getCompanyId()));
  230 +// if (getCompanyId() == 100) {
  231 +// fee.setCompanyName("蓟城山水");
  232 +// } else {
  233 +// // 设置单位id
  234 +// fee.setCompanyName(String.valueOf(companyIdToNamedMap.get(fee.getCompanyId())));
  235 +// }
  236 +// return fee;
  237 +// })
  238 +// .collect(Collectors.toMap(
  239 +// // 生成唯一键:feeMonth + 分隔符 + meterCode(避免不同组合拼接后重复)
  240 +// fee -> fee.getFeeMonth() + "_" + fee.getMeterCode(),
  241 +// // 值为对象本身
  242 +// Function.identity(),
  243 +// // 当键重复时,保留第一个出现的对象(可根据需求改为保留最后一个:(existing, replacement) -> replacement)
  244 +// (existing, replacement) -> replacement
  245 +// ))
  246 +// .values()
  247 +// .stream()
  248 +// .collect(Collectors.toList());
  249 +//
  250 +// if (!CollectionUtils.isEmpty(feeList)) {
  251 +// //查询重复项,进行删除
  252 +// List<Long> ids = waterFeeMapper.selectDuplicateIds(feeList);
  253 +// if (!CollectionUtils.isEmpty(ids)) {
  254 +// waterFeeMapper.deleteByIds(ids);
  255 +// }
  256 +// // 调用批量插入方法
  257 +// waterFeeMapper.insertBatch(feeList);
  258 +// }
  259 +// log.info("成功插入 {} 条水费数据", feeList.size());
  260 +// } else if (PersonalCostImport.class.isAssignableFrom(importClass)) {
  261 +// //去重
  262 +// dataList = personalFeeReplacement((List<PersonalCostImport>) dataList);
  263 +// //获取班组对象
  264 +// Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);
  265 +//
  266 +// // 3. 转换导入数据为PersonalCost列表(Stream优化+空指针防护)
  267 +// List<PersonalCostImport> feeList = (List<PersonalCostImport>)dataList;
  268 +// List<PersonalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
  269 +// if (!CollectionUtils.isEmpty(feeList)) {
  270 +// for (PersonalCostImport importObj : feeList) {
  271 +// PersonalCostDO fee = new PersonalCostDO();
  272 +// BeanUtils.copyProperties(importObj, fee);
  273 +// // 设置单位id
  274 +// fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));
  275 +// //设置班组id
  276 +// //fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));
  277 +// saveFeeList.add(fee);
  278 +// }
  279 +// }
  280 +// // 4. 处理重复数据(删除+插入原子性)
  281 +// if (!feeList.isEmpty()) {
  282 +// // 4.1 查询并删除重复项
  283 +//
  284 +//// List<Long> duplicateIds = personalCostMapper.selectDuplicateIds(saveFeeList);
  285 +//// if (!duplicateIds.isEmpty()) {
  286 +//// personalCostMapper.deleteByIds(duplicateIds);
  287 +//// }
  288 +//
  289 +// // 4.2 批量插入主表
  290 +// personalCostMapper.insertBatch(saveFeeList);
  291 +// }
  292 +// log.info("成功插入 {} 人员费用数据", dataList.size());
  293 +// } else if (DepartmentCostImport.class.isAssignableFrom(importClass)) {
  294 +// //去重
  295 +// dataList = deptFeeReplacement((List<DepartmentCostImport>)dataList);
  296 +// List<DepartmentCostDO> feeList = ((List<DepartmentCostImport>) dataList).stream()
  297 +// .map(importObj -> {
  298 +// DepartmentCostDO fee = new DepartmentCostDO();
  299 +// BeanUtils.copyProperties(importObj, fee);
  300 +// // 设置单位id
  301 +// fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));
  302 +// return fee;
  303 +// })
  304 +// .collect(Collectors.toList());
  305 +// if (!CollectionUtils.isEmpty(feeList)) {
  306 +// //查询重复项,进行删除
  307 +// List<Long> ids = departmentCostMapper.selectDuplicateIds(feeList);
  308 +// if (!CollectionUtils.isEmpty(ids)) {
  309 +// departmentCostMapper.deleteByIds(ids);
  310 +// }
  311 +// // 调用批量插入方法
  312 +// departmentCostMapper.insertBatch(feeList);
  313 +// }
  314 +// log.info("成功插入 {} 综合费用数据", feeList.size());
  315 +// } else if (MechanicalCostImport.class.isAssignableFrom(importClass)) {
  316 +// List<MechanicalCostImport> feeList = (List<MechanicalCostImport>) dataList;
  317 +// List<MechanicalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
  318 +// if (!CollectionUtils.isEmpty(feeList)) {
  319 +// //获取班组对象
  320 +// Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);
  321 +// for (MechanicalCostImport importObj : feeList) {
  322 +// MechanicalCostDO fee = new MechanicalCostDO();
  323 +// BeanUtils.copyProperties(importObj, fee);
  324 +// // 设置单位id
  325 +// fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));
  326 +// //设置班组id
  327 +// fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));
  328 +// // 3.3 计算结束时间(空指针防护)
  329 +// Optional.ofNullable(fee.getStartUseTime())
  330 +// .ifPresent(startTime -> {
  331 +// int useDuration = Optional.ofNullable(fee.getServiceLife())
  332 +// .map(Math::toIntExact) // 处理可能的溢出
  333 +// .orElse(0);
  334 +// fee.setEndUseTime(this.getEndLocalDateForYears(startTime, useDuration));
  335 +// });
  336 +//
  337 +// saveFeeList.add(fee);
  338 +// }
  339 +// }
  340 +// // 4. 批量插入主表(空集合直接跳过)
  341 +// if (!feeList.isEmpty()) {
  342 +// //查询重复项,进行删除
  343 +// List<Long> ids = mechanicalCostMapper.selectDuplicateIds(saveFeeList);
  344 +// if (!CollectionUtils.isEmpty(ids)) {
  345 +// mechanicalCostMapper.deleteByIds(ids);
  346 +// }
  347 +// mechanicalCostMapper.insertBatch(saveFeeList);
  348 +// }
  349 +// log.info("成功插入 {} 自有机械费用数据", dataList.size());
  350 +// } else if (RentalMechanicalCostImport.class.isAssignableFrom(importClass)) {
  351 +// List<RentalMechanicalCostImport> feeList = (List<RentalMechanicalCostImport>)dataList;
  352 +// List<RentalMechanicalCostDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
  353 +// if (!CollectionUtils.isEmpty(feeList)) {
  354 +// //获取班组对象
  355 +// Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);
  356 +// for (RentalMechanicalCostImport importObj : feeList) {
  357 +// RentalMechanicalCostDO fee = new RentalMechanicalCostDO();
  358 +// BeanUtils.copyProperties(importObj, fee);
  359 +// // 设置单位id
  360 +// fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));
  361 +// //设置班组id
  362 +// List<TeamBizdomainRelVo> teamRels = new ArrayList<>();
  363 +// String teamIdsStr = StringUtils.defaultString(importObj.getTeamIds()).replace(",", ",");
  364 +// String weightsStr = StringUtils.defaultString(importObj.getWeight()).replace(",", ",");
  365 +//
  366 +// String[] teamIds = teamIdsStr.split(",");
  367 +// String[] weights = weightsStr.split(",");
  368 +// for (int i = 0; i < teamIds.length; i++) {
  369 +// TeamBizdomainRelVo rel = new TeamBizdomainRelVo();
  370 +// rel.setTeamName(teamIds[i].trim());
  371 +// // 提前处理部门ID映射(复用deptMap)
  372 +// rel.setTeamId(Optional.ofNullable(teamIdMap.get(teamIds[i].trim())).map(String::valueOf).orElse(null));
  373 +// rel.setWeight(Long.valueOf(weights[i].trim()));
  374 +// teamRels.add(rel);
  375 +// }
  376 +// fee.setTeamIds(teamRels);
  377 +//
  378 +// // 3.3 计算结束时间(空指针防护)
  379 +// Optional.ofNullable(fee.getStartUseTime())
  380 +// .ifPresent(startTime -> {
  381 +// int useDuration = Optional.ofNullable(fee.getUseDuration())
  382 +// .map(Math::toIntExact) // 处理可能的溢出
  383 +// .orElse(0);
  384 +// fee.setEndUseTime(getEndLocalDateForYears(startTime, useDuration));
  385 +// });
  386 +//
  387 +// saveFeeList.add(fee);
  388 +// }
  389 +// }
  390 +// if (!feeList.isEmpty()) {
  391 +// rentalMechanicalCostMapper.insertBatch(saveFeeList);
  392 +// }
  393 +// log.info("成功插入 {} 租赁机械费用数据", dataList.size());
  394 +// } else if (GardenWasteRecordImport.class.isAssignableFrom(importClass)) {
  395 +// //获取班组对象
  396 +// List<GardenWasteRecordImport> feeList = (List<GardenWasteRecordImport>)dataList;
  397 +// List<WasteRecordDO> saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size());
  398 +// if (!CollectionUtils.isEmpty(feeList)) {
  399 +// Map<String, Long> teamIdMap = this.getDeptListAll(deptIds);
  400 +// for (GardenWasteRecordImport importObj : feeList) {
  401 +// WasteRecordDO fee = new WasteRecordDO();
  402 +// BeanUtils.copyProperties(importObj, fee);
  403 +// // 设置单位id
  404 +// fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName())));
  405 +// //设置班组id
  406 +// fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds()));
  407 +//
  408 +// saveFeeList.add(fee);
  409 +// }
  410 +// }
  411 +// // 4. 批量插入主表(若主表插入失败,事务回滚)
  412 +// if (!feeList.isEmpty()) {
  413 +// wasteRecordMapper.insertBatch(saveFeeList);
  414 +// }
  415 +//
  416 +// log.info("成功插入 {} 园林废弃物费用数据", dataList.size());
  417 +// }
  418 +//
  419 +// }
  420 +//
  421 +// /**
  422 +// * 从Redis获取分页数据
  423 +// */
  424 +// @Override
  425 +// public <T > CostFeeController.PageResult < T > getPagedDataFromRedis(String batchId, String templateName,
  426 +// int page, int size, Class<T > clazz) throws IOException {
  427 +// String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;
  428 +//
  429 +// // 获取总记录数
  430 +// String totalStr = redisTemplate.opsForValue().get(redisKey + ":total");
  431 +// int total = totalStr != null ? Integer.parseInt(totalStr) : 0;
  432 +//
  433 +// // 计算分页范围
  434 +// int start = (page - 1) * size;
  435 +// int end = Math.min(start + size - 1, total - 1);
  436 +//
  437 +// // 分页查询数据
  438 +// List<T> pageData = new ArrayList<>();
  439 +// if (start <= end && total > 0) {
  440 +// BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");
  441 +//
  442 +// // 批量获取指定范围的索引对应的数据
  443 +// List<String> indexes = IntStream.rangeClosed(start, end)
  444 +// .mapToObj(String::valueOf)
  445 +// .collect(Collectors.toList());
  446 +// List<String> jsonList = hashOps.multiGet(indexes);
  447 +//
  448 +// // 反序列化为对象
  449 +// for (String json : jsonList) {
  450 +// if (json != null) {
  451 +// pageData.add(objectMapper.readValue(json, clazz));
  452 +// }
  453 +// }
  454 +// }
  455 +//
  456 +// return new CostFeeController.PageResult<>(page, size, total, pageData);
  457 +// }
  458 +//
  459 +//
  460 +// /**
  461 +// * 从Redis获取全部数据
  462 +// */
  463 +// private <T > List < T > getAllDataFromRedis(String batchId, String templateName, Class < T > clazz) throws
  464 +// IOException {
  465 +// String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;
  466 +// BoundHashOperations<String, String, String> hashOps = redisTemplate.boundHashOps(redisKey + ":items");
  467 +//
  468 +// // 获取所有数据的JSON字符串
  469 +// List<String> jsonList = hashOps.values();
  470 +// List<T> dataList = new ArrayList<>(jsonList.size());
  471 +//
  472 +// // 反序列化为对象
  473 +// for (String json : jsonList) {
  474 +// if (json != null) {
  475 +// dataList.add(objectMapper.readValue(json, clazz));
  476 +// }
  477 +// }
  478 +//
  479 +// return dataList;
  480 +// }
  481 +//
  482 +//
  483 +// /**
  484 +// * 删除Redis临时数据
  485 +// */
  486 +// private void deleteRedisTempData (String batchId, String templateName){
  487 +// String redisKey = REDIS_KEY_PREFIX + templateName + ":" + batchId;
  488 +// redisTemplate.delete(redisKey + ":total");
  489 +// redisTemplate.delete(redisKey + ":items");
  490 +// log.info("Redis临时数据已删除,批次ID:{}", batchId);
  491 +// }
  492 +// /**
  493 +// * @Author wangqian
  494 +// * @Description 导入内容校验
  495 +// * @Date 2025/11/24 8:40
  496 +// * @Param dataList
  497 +// * @param template
  498 +// * @Return void
  499 +// */
  500 +// public void validateImportData(List dataList, TemplateEnum template) {
  501 +// if (CollectionUtils.isEmpty(dataList)) {
  502 +// return; // 空列表无需校验
  503 +// }
  504 +// //登录用户单位
  505 +// Long belongCompanyId = getCompanyId();
  506 +// //单位集合
  507 +// List<DeptRespDTO> companyList = deptApi.getSubDeptList();
  508 +// List<Long> deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList());
  509 +// Map<String, Long> nameToCompanyIdMap = CollectionUtils.isEmpty(companyList)
  510 +// ? Map.of() // 空列表返回空不可变 Map(避免 null)
  511 +// : companyList.stream()
  512 +// // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整)
  513 +// .collect(Collectors.toMap(
  514 +// dept -> dept.getName().trim(), // value 映射:取 companyName(去空格)
  515 +// DeptRespDTO::getId, // key 映射:取 companyId
  516 +// (oldValue, newValue) -> newValue
  517 +// ));
  518 +// //班组集合
  519 +// Map<Long, List<String>> deptList = this.getDetpList(deptIds);
  520 +// // 模板类型校验
  521 +// Class<?> importClass = template.getImportClass(); // 从枚举获取导入类,避免switch
  522 +// // 根据不同模板类型执行不同的处理逻辑
  523 +// if (WaterFeeImport.class.isAssignableFrom(importClass)) {
  524 +//
  525 +// List<WaterFeeImport> importList = (List<WaterFeeImport>) dataList;
  526 +//
  527 +// // 2. 遍历,逐个创建
  528 +// for (int i = 0; i < importList.size(); i++) {
  529 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  530 +// WaterFeeImport importBean = importList.get(i);
  531 +//
  532 +// try {
  533 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  534 +// Set<ConstraintViolation<WaterFeeImport>> violations = validator.validate(importBean);
  535 +// if (!violations.isEmpty()) {
  536 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  537 +// // StringBuilder errorMsg = new StringBuilder();
  538 +// for (ConstraintViolation<WaterFeeImport> violation : violations) {
  539 +// //errorMsg.append(violation.getMessage()).append(";");
  540 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  541 +// "第[{}]条数据:"+ violation.getMessage()
  542 +// ), rowNum);
  543 +// }
  544 +// // 抛出包含行号和所有错误的异常
  545 +// //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);
  546 +// }
  547 +//
  548 +// } catch (ConstraintViolationException ex) {
  549 +// // 5. 包装异常,明确行号 + 真实校验错误(
  550 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  551 +// }
  552 +// }
  553 +// }
  554 +// else if (PersonalCostImport.class.isAssignableFrom(importClass)) {
  555 +//
  556 +// List<PersonalCostImport> importList = (List<PersonalCostImport>) dataList;
  557 +// // 2. 遍历,逐个创建
  558 +// for (int i = 0; i < importList.size(); i++) {
  559 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  560 +// PersonalCostImport importBean = importList.get(i);
  561 +//
  562 +// try {
  563 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  564 +// Set<ConstraintViolation<PersonalCostImport>> violations = validator.validate(importBean);
  565 +// if (!violations.isEmpty()) {
  566 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  567 +// //StringBuilder errorMsg = new StringBuilder();
  568 +// for (ConstraintViolation<PersonalCostImport> violation : violations) {
  569 +// // errorMsg.append(violation.getMessage()).append(";");
  570 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  571 +// "第[{}]条数据:"+ violation.getMessage()
  572 +// ), rowNum);
  573 +// }
  574 +// // 抛出包含行号和所有错误的异常
  575 +// // throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);
  576 +// }
  577 +// Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());
  578 +// //单位校验
  579 +// validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);
  580 +// //部门名称校验
  581 +// validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());
  582 +//
  583 +// } catch (ConstraintViolationException ex) {
  584 +// // 5. 包装异常,明确行号 + 真实校验错误(
  585 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  586 +// }
  587 +// }
  588 +// }
  589 +// else if (GardenWasteRecordImport.class.isAssignableFrom(importClass)) {
  590 +//
  591 +// List<GardenWasteRecordImport> importList = (List<GardenWasteRecordImport>) dataList;
  592 +// // 2. 遍历,逐个创建
  593 +// for (int i = 0; i < importList.size(); i++) {
  594 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  595 +// GardenWasteRecordImport importBean = importList.get(i);
  596 +//
  597 +// try {
  598 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  599 +// Set<ConstraintViolation<GardenWasteRecordImport>> violations = validator.validate(importBean);
  600 +// if (!violations.isEmpty()) {
  601 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  602 +// //StringBuilder errorMsg = new StringBuilder();
  603 +// for (ConstraintViolation<GardenWasteRecordImport> violation : violations) {
  604 +// //errorMsg.append(violation.getMessage()).append(";");
  605 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  606 +// "第[{}]条数据:"+ violation.getMessage()
  607 +// ), rowNum);
  608 +// }
  609 +// // 抛出包含行号和所有错误的异常
  610 +// //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations);
  611 +// }
  612 +// Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());
  613 +// //单位校验
  614 +// validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);
  615 +// //部门名称校验
  616 +// validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());
  617 +//
  618 +// } catch (ConstraintViolationException ex) {
  619 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  620 +// }
  621 +// }
  622 +// }
  623 +// else if (MechanicalCostImport.class.isAssignableFrom(importClass)) {
  624 +//
  625 +// List<MechanicalCostImport> importList = (List<MechanicalCostImport>) dataList;
  626 +// // 2. 遍历,逐个创建
  627 +// for (int i = 0; i < importList.size(); i++) {
  628 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  629 +// MechanicalCostImport importBean = importList.get(i);
  630 +//
  631 +// try {
  632 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  633 +// Set<ConstraintViolation<MechanicalCostImport>> violations = validator.validate(importBean);
  634 +// if (!violations.isEmpty()) {
  635 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  636 +// //StringBuilder errorMsg = new StringBuilder();
  637 +// for (ConstraintViolation<MechanicalCostImport> violation : violations) {
  638 +// //errorMsg.append(violation.getMessage()).append(";");
  639 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  640 +// "第[{}]条数据:"+ violation.getMessage()
  641 +// ), rowNum);
  642 +// }
  643 +// // 抛出包含行号和所有错误的异常
  644 +// //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);
  645 +// }
  646 +// Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());
  647 +// //单位校验
  648 +// validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);
  649 +// //部门名称校验
  650 +// validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());
  651 +//
  652 +// //车辆校验
  653 +// if (CostFeeConstants.MECH_TYPE.equals(importBean.getMechType())) {
  654 +// if (StringUtils.isEmpty(importBean.getLicensePlate())) {
  655 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  656 +// "第[{}]条数据:车牌号不能为空"
  657 +// ), rowNum);
  658 +// }
  659 +// if (StringUtils.isEmpty(importBean.getVehicleType())) {
  660 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  661 +// "第[{}]条数据:车辆类型不能为空"
  662 +// ), rowNum);
  663 +//
  664 +// }
  665 +// if (StringUtils.isEmpty(importBean.getVehicleCategory())) {
  666 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  667 +// "第%d条数据:车辆类别不能为空"
  668 +// ), rowNum);
  669 +// }
  670 +// }
  671 +//
  672 +// } catch (ConstraintViolationException ex) {
  673 +// // 5. 包装异常,明确行号 + 真实校验错误(
  674 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  675 +// }
  676 +// }
  677 +// }
  678 +// else if (RentalMechanicalCostImport.class.isAssignableFrom(importClass)) {
  679 +//
  680 +// List<RentalMechanicalCostImport> importList = (List<RentalMechanicalCostImport>) dataList;
  681 +// // 2. 遍历,逐个创建
  682 +// for (int i = 0; i < importList.size(); i++) {
  683 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  684 +// RentalMechanicalCostImport importBean = importList.get(i);
  685 +//
  686 +// try {
  687 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  688 +// Set<ConstraintViolation<RentalMechanicalCostImport>> violations = validator.validate(importBean);
  689 +// if (!violations.isEmpty()) {
  690 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  691 +// // StringBuilder errorMsg = new StringBuilder();
  692 +// for (ConstraintViolation<RentalMechanicalCostImport> violation : violations) {
  693 +// // errorMsg.append(violation.getMessage()).append(";");
  694 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  695 +// "第[{}]条数据:"+violation.getMessage()
  696 +// ), rowNum);
  697 +// }
  698 +// // 抛出包含行号和所有错误的异常
  699 +// //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);
  700 +// }
  701 +// // 处理可能的空值
  702 +// String teamIdsStr = StringUtils.defaultString(importBean.getTeamIds());
  703 +// String weightsStr = StringUtils.defaultString(importBean.getWeight());
  704 +//
  705 +// // 统一替换中文逗号并分割
  706 +// String[] teamIds = teamIdsStr.replace(",", ",").split(",");
  707 +// String[] weights = weightsStr.replace(",", ",").split(",");
  708 +//
  709 +// // 校验数组长度是否一致
  710 +// if (teamIds.length != weights.length) {
  711 +// throw exception0(BAD_REQUEST.getCode(), String.format(
  712 +// "第%d条数据:权重值和所选班组数量不符(班组:%d个,权重:%d个)",
  713 +// rowNum, teamIds.length, weights.length
  714 +// ));
  715 +// }
  716 +// Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());
  717 +// //单位校验
  718 +// validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);
  719 +// //部门名称校验
  720 +// validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds());
  721 +//
  722 +// } catch (ConstraintViolationException ex) {
  723 +// // 5. 包装异常,明确行号 + 真实校验错误(
  724 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  725 +// }
  726 +// }
  727 +// }
  728 +// else if (DepartmentCostImport.class.isAssignableFrom(importClass)) {
  729 +//
  730 +// List<DepartmentCostImport> importList = (List<DepartmentCostImport>) dataList;
  731 +// // 2. 遍历,逐个创建
  732 +// for (int i = 0; i < importList.size(); i++) {
  733 +// int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑)
  734 +// DepartmentCostImport importBean = importList.get(i);
  735 +//
  736 +// try {
  737 +// // 2.1 执行校验(替换原 ValidationUtils,直接用 Validator 校验,确保抛出 ConstraintViolationException)
  738 +// Set<ConstraintViolation<DepartmentCostImport>> violations = validator.validate(importBean);
  739 +// if (!violations.isEmpty()) {
  740 +// // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”)
  741 +// //StringBuilder errorMsg = new StringBuilder();
  742 +// for (ConstraintViolation<DepartmentCostImport> violation : violations) {
  743 +// //errorMsg.append(violation.getMessage()).append(";");
  744 +// throw exception0(BAD_REQUEST.getCode() ,String.format(
  745 +// "第[{}]条数据:"+violation.getMessage()
  746 +// ), rowNum);
  747 +// }
  748 +// // 抛出包含行号和所有错误的异常
  749 +// //throw new ConstraintViolationException(errorMsg.toString().trim(), violations);
  750 +// }
  751 +//
  752 +// Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName());
  753 +// //单位校验
  754 +// validatecompany(rowNum, importBean.getCompanyName(), companyId, belongCompanyId);
  755 +// } catch (ConstraintViolationException ex) {
  756 +// // 5. 包装异常,明确行号 + 真实校验错误(
  757 +// throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex);
  758 +// }
  759 +// }
  760 +// }
  761 +// }
  762 +//
  763 +// private void validatecompany(int rowNum, String companyName, Long companyId, Long belongCompanyId){
  764 +// // 校验数组长度是否一致
  765 +// if (companyId == null) {
  766 +// throw exception0(BAD_REQUEST.getCode(), String.format(
  767 +// "第%d条数据:单位名称不正确(%s)",
  768 +// rowNum, companyName
  769 +// ));
  770 +// }
  771 +// // 校验导入单位
  772 +// if (belongCompanyId != 100 && !belongCompanyId.equals(companyId)) {
  773 +// throw exception0(BAD_REQUEST.getCode(), String.format(
  774 +// "第%d条数据:导入单位%s与当前操作员单位不一致",
  775 +// rowNum, companyName
  776 +// ));
  777 +// }
  778 +// }
  779 +// private void validateTeams(int rowNum, List<String> dept, String teamIds) {
  780 +// if (StringUtils.isNotEmpty(teamIds)) {
  781 +// String[] teamNames = teamIds.split(",");
  782 +// Set<String> checkedTeams = new HashSet<>(); // 用于存储已校验的团队名称,检测重复
  783 +//
  784 +// for (String teamName : teamNames) {
  785 +// String trimmedTeamName = teamName.trim(); // 处理前后空格
  786 +//
  787 +// // 重复校验:如果Set中已存在该团队名称,说明重复
  788 +// if (checkedTeams.contains(trimmedTeamName)) {
  789 +// throw exception0(BAD_REQUEST.getCode(), String.format(
  790 +// "第%d条数据:%s名称重复",
  791 +// rowNum, teamName
  792 +// ));
  793 +// }
  794 +//
  795 +// // 存在性校验:检查团队是否在dept中存在
  796 +// if (!dept.contains(trimmedTeamName)) {
  797 +// throw exception0(BAD_REQUEST.getCode(), String.format(
  798 +// "第%d条数据:%s名称不正确",
  799 +// rowNum, teamName
  800 +// ));
  801 +// }
  802 +//
  803 +// // 将校验通过的团队名称加入Set,用于后续重复判断
  804 +// checkedTeams.add(trimmedTeamName);
  805 +// }
  806 +// }
  807 +// }
  808 +//
  809 +// //查询单位下的班组信息
  810 +// private Map<Long,List<String>> getDetpList (List<Long> deptIds) {
  811 +// Map<Long,List<String>> listMap = new HashMap<>();
  812 +// for (Long depId : deptIds) {
  813 +// List<DeptRespDTO> deptList = deptApi.getChildDeptList(depId);
  814 +// List<String> names = deptList.stream().map(DeptRespDTO::getName).collect(Collectors.toList());
  815 +// listMap.put(depId,names);
  816 +// }
  817 +// return listMap;
  818 +// }
  819 +//
  820 +// /**
  821 +// * 按salaryMonth、expenseName、companyId三个字段去重
  822 +// * @param costList 原始部门成本列表
  823 +// * @return 去重后的列表
  824 +// */
  825 +// public List<DepartmentCostImport> deptFeeReplacement(List<DepartmentCostImport> costList) {
  826 +// if (costList == null || costList.isEmpty()) {
  827 +// return new ArrayList<>();
  828 +// }
  829 +//
  830 +// // 1. 以三个字段的组合作为键(使用分隔符避免不同组合产生相同键)
  831 +// // 2. 遇到重复键时保留第一条记录
  832 +// Map<String, DepartmentCostImport> uniqueMap = costList.stream()
  833 +// .collect(Collectors.toMap(
  834 +// // 生成唯一键:salaryMonth|expenseName|companyId
  835 +// cost -> buildUniqueKey(cost),
  836 +// // 键不存在时存入当前对象
  837 +// cost -> cost,
  838 +// // 键已存在时保留最后一个
  839 +// (existing, replacement) -> replacement
  840 +// ));
  841 +//
  842 +// // 将去重后的结果转为List返回
  843 +// return new ArrayList<>(uniqueMap.values());
  844 +// }
  845 +//
  846 +// /**
  847 +// * 构建三个字段的唯一组合键(处理null值)
  848 +// */
  849 +// private String buildUniqueKey(DepartmentCostImport cost) {
  850 +// // 使用Objects.toString处理可能的null值,避免NullPointerException
  851 +// return Objects.toString(cost.getSalaryMonth(), "") + "|" +
  852 +// Objects.toString(cost.getExpenseName(), "") + "|" +
  853 +// Objects.toString(cost.getCompanyName(), "");
  854 +// }
  855 +//
  856 +// /**
  857 +// * 按salaryMonth、name、companyId、teamIdList四个字段去重
  858 +// * @param costList 原始个人成本列表
  859 +// * @return 去重后的列表
  860 +// */
  861 +// public List<PersonalCostImport> personalFeeReplacement(List<PersonalCostImport> costList) {
  862 +// if (costList == null || costList.isEmpty()) {
  863 +// return new ArrayList<>();
  864 +// }
  865 +//
  866 +// // 1. 以四个字段的组合作为键(特别处理集合类型的teamIdList)
  867 +// // 2. 遇到重复键时保留第一条记录
  868 +// Map<String, PersonalCostImport> uniqueMap = costList.stream()
  869 +// .collect(Collectors.toMap(
  870 +// // 生成唯一键:salaryMonth|name|companyId|teamIdListStr
  871 +// this::buildUniqueKey,
  872 +// // 键不存在时存入当前对象
  873 +// cost -> cost,
  874 +// // 键已存在时保留第一个对象(可改为replacement保留最后一个)
  875 +// (existing, replacement) -> replacement
  876 +// ));
  877 +//
  878 +// // 将去重后的结果转为List返回
  879 +// return new ArrayList<>(uniqueMap.values());
  880 +// }
  881 +//
  882 +// /**
  883 +// * 构建四个字段的唯一组合键(处理null值和集合类型)
  884 +// */
  885 +// private String buildUniqueKey(PersonalCostImport cost) {
  886 +// // 处理普通字段的null值
  887 +// String salaryMonth = Objects.toString(cost.getSalaryMonth(), "");
  888 +// String name = Objects.toString(cost.getName(), "");
  889 +// String companyId = Objects.toString(cost.getCompanyName(), "");
  890 +// String teamIds = Objects.toString(cost.getTeamIds(), "");
  891 +// // 拼接唯一键
  892 +// return salaryMonth + "|" + name + "|" + companyId + "|" + teamIds;
  893 +// }
  894 +//
  895 +//
  896 +// public static LocalDate getEndLocalDateForYears(LocalDate startDate, int years) {
  897 +// if (startDate == null) {
  898 +// throw new IllegalArgumentException("开始日期不能为null");
  899 +// }
  900 +// if (years < 0) {
  901 +// throw new IllegalArgumentException("年数不能为负数");
  902 +// }
  903 +// return startDate.plusYears(years);
  904 +// }
  905 +//
  906 +// /**
  907 +// * @Author wangqian
  908 +// * @Description 根据班组名称获取班组id
  909 +// * @Date 2025/11/26 19:38
  910 +// */
  911 +// /**
  912 +// * 根据部门ID列表获取「部门名称→部门ID」的映射(含空值防护)
  913 +// */
  914 +// private Map<String, Long> getDeptListAll(List<Long> deptIds) {
  915 +// Map<String, Long> deptNameMap = new HashMap<>();
  916 +//
  917 +// // 1. 空值安全:避免 deptIds 为 null/空列表导致 NPE
  918 +// if (CollectionUtils.isEmpty(deptIds)) {
  919 +// return deptNameMap;
  920 +// }
  921 +//
  922 +// for (Long deptId : deptIds) {
  923 +// // 2. 跳过 null 的部门ID
  924 +// if (deptId == null) {
  925 +// continue;
  926 +// }
  927 +//
  928 +// // 3. 获取子部门列表(防护接口返回 null)
  929 +// List<DeptRespDTO> deptList = deptApi.getChildDeptList(deptId);
  930 +// if (CollectionUtils.isEmpty(deptList)) {
  931 +// continue;
  932 +// }
  933 +//
  934 +// // 4. 遍历子部门:过滤无效名称,避免 Map 出现 null/空字符串键
  935 +// for (DeptRespDTO dept : deptList) {
  936 +// if (dept == null) {
  937 +// continue;
  938 +// }
  939 +// String deptName = dept.getName();
  940 +// // 过滤 null/空白名称(避免 Map 键为 null 或空字符串)
  941 +// if (deptName == null || deptName.trim().isEmpty()) {
  942 +// continue;
  943 +// }
  944 +// // 若名称重复,后遍历的部门会覆盖先遍历的(可根据业务调整规则)
  945 +// deptNameMap.put(deptName.trim(), dept.getId());
  946 +// }
  947 +// }
  948 +//
  949 +// return deptNameMap;
  950 +// }
  951 +// /**
  952 +// * @Author wangqian
  953 +// * @Description 根据班组名称获取班组id集合
  954 +// * @Date 2025/11/26 20:07
  955 +// */
  956 +// private List<String> getDeptList(Map<String, Long> teamIdMap, String teamId) {
  957 +// List<String> list = new ArrayList<>();
  958 +//
  959 +// // 1. 处理 teamId 为空的情况,直接返回空列表
  960 +// if (StringUtils.isEmpty(teamId)) {
  961 +// return list;
  962 +// }
  963 +//
  964 +// // 2. 分割字符串,处理空元素
  965 +// String[] teamArray = teamId.split(",");
  966 +// for (String teamName : teamArray) {
  967 +// // 3. 去除首尾空格,避免因空格导致匹配不到
  968 +// String trimTeamName = teamName.trim();
  969 +// if (StringUtils.isEmpty(trimTeamName)) {
  970 +// continue; // 跳过空字符串元素
  971 +// }
  972 +//
  973 +// // 4. 从Map中获取ID,处理null值(避免添加null到列表)
  974 +// Long deptId = teamIdMap.get(trimTeamName);
  975 +// if (deptId != null) {
  976 +// list.add(String.valueOf(deptId)); // 直接添加Long类型,修正原String类型错误
  977 +// }
  978 +// }
  979 +//
  980 +// return list;
  981 +// }
  982 +//}
urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/auth/vo/AuthPermissionInfoRespVO.java
@@ -44,6 +44,13 @@ public class AuthPermissionInfoRespVO { @@ -44,6 +44,13 @@ public class AuthPermissionInfoRespVO {
44 @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg") 44 @Schema(description = "用户头像", requiredMode = Schema.RequiredMode.REQUIRED, example = "https://www.iocoder.cn/xx.jpg")
45 private String avatar; 45 private String avatar;
46 46
  47 + @Schema(description = "公司ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
  48 + private Long companyId;
  49 +
  50 + @Schema(description = "业务线", requiredMode = Schema.RequiredMode.REQUIRED, example = "1001")
  51 + private String busiLine;
  52 +
  53 +
47 @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048") 54 @Schema(description = "部门编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
48 private Long deptId; 55 private Long deptId;
49 56