Commit f8df719745e36291b0a09501570adaa6c2f5f953
Merge branch 'dev' into de_wangfs_202511
Showing
8 changed files
with
1477 additions
and
1432 deletions
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()); | ... | ... |