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 43 private BigDecimal totalCost;
44 44  
45 45 @Schema(description = "归属公司", requiredMode = Schema.RequiredMode.REQUIRED, example = "张三")
46   - @NotEmpty(message = "归属公司不能为空")
  46 + //@NotEmpty(message = "归属公司不能为空")
47 47 private String companyName;
48 48  
49 49 @Schema(description = "归属公司ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "3314")
50   - @NotEmpty(message = "归属公司ID不能为空")
  50 + //@NotEmpty(message = "归属公司ID不能为空")
51 51 private String companyId;
52 52  
53 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 10 import com.zteits.urbanops.module.garden.controller.admin.materialinventory.vo.MaterialInventorySaveReqVO;
11 11 import com.zteits.urbanops.module.garden.dal.dataobject.materialinventory.MaterialInventoryDO;
12 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 15 import jakarta.annotation.Resource;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.validation.annotation.Validated;
... ... @@ -19,6 +21,7 @@ import java.util.List;
19 21 import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
20 22 import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception;
21 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 25 import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.*;
23 26  
24 27 /**
... ... @@ -33,10 +36,19 @@ public class MaterialInventoryServiceImpl implements MaterialInventoryService {
33 36 @Resource
34 37 private MaterialInventoryMapper materialInventoryMapper;
35 38  
  39 + @Resource
  40 + private DeptApi deptApi;
  41 +
36 42 @Override
37 43 public Long createMaterialInventory(MaterialInventorySaveReqVO createReqVO) {
38 44 // 插入
39 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 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 import com.zteits.urbanops.module.garden.dal.dataobject.materialinventory.MaterialInventoryDO;
7 7 import com.zteits.urbanops.module.garden.dal.mysql.material.MaterialMapper;
8 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 11 import org.springframework.stereotype.Service;
10 12 import jakarta.annotation.Resource;
11 13 import org.springframework.validation.annotation.Validated;
... ... @@ -25,8 +27,7 @@ import static com.zteits.urbanops.framework.common.exception.util.ServiceExcepti
25 27 import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception0;
26 28 import static com.zteits.urbanops.framework.common.util.collection.CollectionUtils.convertList;
27 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 31 import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.*;
31 32  
32 33 /**
... ... @@ -47,6 +48,9 @@ public class MaterialInventoryInServiceImpl implements MaterialInventoryInServic
47 48 @Resource
48 49 private MaterialInventoryService materialInventoryService;
49 50  
  51 + @Resource
  52 + private DeptApi deptApi;
  53 +
50 54 @Override
51 55 @Transactional
52 56 public Long createMaterialInventoryIn(MaterialInventoryInSaveReqVO createReqVO) {
... ... @@ -55,6 +59,10 @@ public class MaterialInventoryInServiceImpl implements MaterialInventoryInServic
55 59 if(null == material){
56 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 66 MaterialInventoryInDO in = new MaterialInventoryInDO();
59 67 in.setInventoryInNo(System.currentTimeMillis()+ RandomUtil.randomInt(10, 100000)+"");
60 68 in.setMaterialId(createReqVO.getMaterialId());
... ... @@ -66,8 +74,10 @@ public class MaterialInventoryInServiceImpl implements MaterialInventoryInServic
66 74 in.setTypeDetailId(material.getTypeDetailId());
67 75 in.setTypeDetailName(material.getTypeDetailName());
68 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 81 in.setInInventoryNum(createReqVO.getInInventoryNum());
72 82 in.setMaterialPrice(createReqVO.getMaterialPrice());
73 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 10 import com.zteits.urbanops.module.garden.dal.mysql.material.MaterialMapper;
11 11 import com.zteits.urbanops.module.garden.dal.mysql.materialinventoryout.MaterialInventoryOutMapper;
12 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 15 import jakarta.annotation.Resource;
14 16 import org.springframework.stereotype.Service;
15 17 import org.springframework.validation.annotation.Validated;
... ... @@ -19,6 +21,7 @@ import java.util.List;
19 21 import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
20 22 import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception;
21 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 25 import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.*;
23 26  
24 27 /**
... ... @@ -37,6 +40,9 @@ public class MaterialInventoryOutServiceImpl implements MaterialInventoryOutServ
37 40  
38 41 @Resource
39 42 private MaterialInventoryService materialInventoryService;
  43 +
  44 + @Resource
  45 + private DeptApi deptApi;
40 46 @Override
41 47 public Long createMaterialInventoryOut(MaterialInventoryOutSaveReqVO createReqVO) {
42 48 // 插入
... ... @@ -54,7 +60,10 @@ public class MaterialInventoryOutServiceImpl implements MaterialInventoryOutServ
54 60 if(materialInventory.getCurrentNum() < out.getOutInventoryNum()){
55 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 67 out.setMaterialName(material.getMaterialName());
59 68 out.setMaterialTypeId(material.getMaterialTypeId());
60 69 out.setMaterialTypeName(material.getMaterialTypeName());
... ... @@ -63,8 +72,10 @@ public class MaterialInventoryOutServiceImpl implements MaterialInventoryOutServ
63 72 out.setTypeDetailId(material.getTypeDetailId());
64 73 out.setTypeDetailName(material.getTypeDetailName());
65 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 79 out.setUnitId(material.getUnitId());
69 80 out.setUnitName(material.getUnitName());
70 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 7 import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.TeamBizdomainRelVo;
8 8 import com.zteits.urbanops.module.garden.dal.dataobject.wasterecord.WasteRecordDO;
9 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 12 import jakarta.annotation.Resource;
11 13 import org.springframework.stereotype.Service;
12 14 import org.springframework.transaction.annotation.Transactional;
... ... @@ -15,7 +17,10 @@ import org.springframework.validation.annotation.Validated;
15 17 import java.util.List;
16 18 import java.util.stream.Collectors;
17 19  
  20 +import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
18 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 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 35 @Resource
31 36 private WasteRecordMapper wasteRecordMapper;
32 37  
  38 + @Resource
  39 + private DeptApi deptApi;
33 40 @Override
34 41 @Transactional
35 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 48 WasteRecordDO wasteRecord = BeanUtils.toBean(createReqVO, WasteRecordDO.class);
  49 + wasteRecord.setCompanyId(String.valueOf(deptRespDTO.getId()));
  50 + wasteRecord.setCompanyName(deptRespDTO.getName());
38 51 List<Long> deptIds = createReqVO.getTeamIds().stream()
39 52 .map(TeamBizdomainRelVo::getTeamId)
40 53 .collect(Collectors.toList());
... ...