Commit 5dfcf12b219c29d59c2f1e9c8366148c35c7ccde

Authored by wangqian
1 parent 5f5a817e

耗材修改

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