From 2672c24ebc52bee62b7e7e9e8f72a09f937b2cdd Mon Sep 17 00:00:00 2001 From: wangqian <1406304305@qq.com> Date: Tue, 25 Nov 2025 20:59:07 +0800 Subject: [PATCH] 派单及人机材功能迁移 --- urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java | 62 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java | 153 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------------------------------------------ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApi.java | 7 +++++++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApiImpl.java | 9 +++++++++ urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/dept/DeptController.java | 1 + 5 files changed, 190 insertions(+), 42 deletions(-) diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java index f36929b..7b6094e 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java @@ -2,6 +2,7 @@ package com.zteits.urbanops.module.garden.controller.admin.costfee; import com.zteits.urbanops.framework.common.pojo.CommonResult; import com.zteits.urbanops.framework.excel.core.util.ExcelUtils; +import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.WaterFeeImport; import com.zteits.urbanops.module.garden.enums.TemplateEnum; import com.zteits.urbanops.module.garden.service.costfee.CostFeeService; import io.swagger.v3.oas.annotations.Operation; @@ -19,6 +20,7 @@ import org.springframework.web.multipart.MultipartFile; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; +import java.lang.reflect.Method; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.*; @@ -277,6 +279,8 @@ public class CostFeeController{ if (dataList == null) { dataList = Collections.emptyList(); } + // 关键:移除最后一行(以“说明”开头的行) + removeLastLineIfStartWithExplain(dataList, importClass); // 基础数据校验(非空、格式等) costFeeService.validateImportData(dataList, template); @@ -285,7 +289,65 @@ public class CostFeeController{ } return new ImportResult<>(dataList, template.isNeedPreview()); } + /** + * 移除列表最后一行(若该行第一列内容以“说明”开头) + * @param dataList Excel解析后的实体类列表 + * @param clazz 实体类字节码(用于反射获取第一列字段值) + * @param 泛型:Excel对应的实体类 + */ + private void removeLastLineIfStartWithExplain(List dataList, Class clazz) { + // 1. 跳过空列表/元素数<1的情况,避免索引越界 + if (CollectionUtils.isEmpty(dataList)) { + return; + } + + // 2. 获取最后一个元素(正确索引:size()-1) + T lastItem = dataList.get(dataList.size() - 1); + if (Objects.isNull(lastItem)) { + return; + } + + // 3. 反射获取「第一列字段的值」(核心:Excel列对应实体类的字段,假设第一列字段名是 firstColumn,需根据实际调整) + String firstColumnValue = getFirstColumnValue(lastItem, clazz); + if (Objects.isNull(firstColumnValue)) { + return; + } + + // 4. 匹配“说明”(trim() 避免空格干扰,如“ 说明:xxx”) + if (firstColumnValue.trim().startsWith("说明")) { + // 移除最后一个元素(正确索引:size()-1) + dataList.remove(dataList.size() - 1); + } + } + /** + * 反射获取实体类的「第一列字段值」(需根据你的Excel列配置调整字段名) + * @param item 实体类对象(Excel一行数据) + * @param clazz 实体类字节码 + * @return 第一列的字符串值(null 表示无值) + */ + private String getFirstColumnValue(T item, Class clazz) { + try { + String firstColumnFieldName = ""; + if (WaterFeeImport.class.isAssignableFrom(clazz)) { + firstColumnFieldName = "feeMonth"; + } else { + return null; + } + + // 反射获取 getter 方法() + String getterMethodName = "get" + firstColumnFieldName.substring(0, 1).toUpperCase() + firstColumnFieldName.substring(1); + Method getterMethod = clazz.getDeclaredMethod(getterMethodName); + + // 执行 getter 方法获取字段值 + Object value = getterMethod.invoke(item); + return value == null ? null : value.toString(); + } catch (Exception e) { + // 反射失败(字段名错误、无getter方法),打印日志不抛异常,避免影响整体流程 + log.warn("获取Excel第一列字段值失败,实体类:{}", clazz.getName(), e); + return null; + } + } /** * 导入结果封装 diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java index f1d5098..bea389e 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java @@ -5,7 +5,11 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.zteits.urbanops.framework.security.core.LoginUser; import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController; import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.*; +import com.zteits.urbanops.module.garden.enums.AssignTasksConstants; +import com.zteits.urbanops.module.garden.enums.CostFeeConstants; import com.zteits.urbanops.module.garden.enums.TemplateEnum; +import com.zteits.urbanops.module.system.api.dept.DeptApi; +import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO; import jakarta.annotation.Resource; import jakarta.validation.ConstraintViolation; import jakarta.validation.ConstraintViolationException; @@ -25,7 +29,9 @@ import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; import java.util.stream.IntStream; +import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception; +import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception0; import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getLoginUser; import static com.zteits.urbanops.framework.security.core.util.SecurityFrameworkUtils.getLoginUserDeptId; import static com.zteits.urbanops.module.garden.enums.ErrorCodeConstants.COST_FEE_NOT_EXISTS_001; @@ -50,7 +56,8 @@ public class CostFeeServiceImpl implements CostFeeService { private ObjectMapper objectMapper; @Resource private Validator validator; - + @Resource + private DeptApi deptApi; @Override /** @@ -229,14 +236,29 @@ public class CostFeeServiceImpl implements CostFeeService { if (CollectionUtils.isEmpty(dataList)) { return; // 空列表无需校验 } - LoginUser user = getLoginUser(); + //登录用户部门 Long deptId = getLoginUserDeptId(); + //单位集合 + List companyList = deptApi.getSubDeptList(); + List deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList()); + Map nameToCompanyIdMap = CollectionUtils.isEmpty(companyList) + ? Map.of() // 空列表返回空不可变 Map(避免 null) + : companyList.stream() + // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整) + .collect(Collectors.toMap( + dept -> dept.getName().trim(), // value 映射:取 companyName(去空格) + DeptRespDTO::getId, // key 映射:取 companyId + (oldValue, newValue) -> newValue + )); + //班组集合 + Map> deptList = this.getDetpList(deptIds); // 模板类型校验 Class importClass = template.getImportClass(); // 从枚举获取导入类,避免switch // 根据不同模板类型执行不同的处理逻辑 if (WaterFeeImport.class.isAssignableFrom(importClass)) { List importList = (List) dataList; + // 2. 遍历,逐个创建 for (int i = 0; i < importList.size(); i++) { int rowNum = i + 1; // 第i条数据 → 第rowNum行(对应Excel导入的行号逻辑) @@ -247,18 +269,20 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + // StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + //errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+ violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations); } - } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } @@ -275,17 +299,20 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + //StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + // errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+ violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + // throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations); } } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } @@ -302,18 +329,20 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + //StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + //errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+ violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations); } } catch (ConstraintViolationException ex) { - // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } @@ -330,18 +359,45 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + //StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + //errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+ violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + //throw new ConstraintViolationException(errorMsg.toString().trim(), violations); } + Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName()); + //单位校验 + validatecompany(rowNum, importBean.getCompanyName(), companyId, deptId); + //部门名称校验 + validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds()); + + //车辆校验 + if (CostFeeConstants.MECH_TYPE.equals(importBean.getMechType())) { + if (StringUtils.isEmpty(importBean.getLicensePlate())) { + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:车牌号不能为空" + ), rowNum); + } + if (StringUtils.isEmpty(importBean.getVehicleType())) { + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:车辆类型不能为空" + ), rowNum); + } + if (StringUtils.isEmpty(importBean.getVehicleCategory())) { + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第%d条数据:车辆类别不能为空" + ), rowNum); + } + } } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } @@ -358,12 +414,15 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + // StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + // errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + //throw new ConstraintViolationException(errorMsg.toString().trim(), violations); } // 处理可能的空值 String teamIdsStr = StringUtils.defaultString(importBean.getTeamIds()); @@ -375,22 +434,20 @@ public class CostFeeServiceImpl implements CostFeeService { // 校验数组长度是否一致 if (teamIds.length != weights.length) { - throw new IllegalArgumentException(String.format( + throw exception0(BAD_REQUEST.getCode(), String.format( "第%d条数据:权重值和所选班组数量不符(班组:%d个,权重:%d个)", rowNum, teamIds.length, weights.length )); } - + Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName()); //单位校验 - // validatecompany(rowNum, importBean.getCompanyName(), deptId); + validatecompany(rowNum, importBean.getCompanyName(), companyId, deptId); //部门名称校验 - //validateTeams(rowNum, depts.get(data.getCompanyName()), data.getTeamIds()); - - + validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds()); } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } @@ -407,36 +464,37 @@ public class CostFeeServiceImpl implements CostFeeService { Set> violations = validator.validate(importBean); if (!violations.isEmpty()) { // 收集所有校验错误信息(比如“用水量不能为空”“格式应为两位小数”) - StringBuilder errorMsg = new StringBuilder(); + //StringBuilder errorMsg = new StringBuilder(); for (ConstraintViolation violation : violations) { - errorMsg.append(violation.getMessage()).append(";"); + //errorMsg.append(violation.getMessage()).append(";"); + throw exception0(BAD_REQUEST.getCode() ,String.format( + "第[{}]条数据:"+violation.getMessage() + ), rowNum); } // 抛出包含行号和所有错误的异常 - throw new ConstraintViolationException(errorMsg.toString().trim(), violations); + //throw new ConstraintViolationException(errorMsg.toString().trim(), violations); } } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( - throw new RuntimeException(String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); + throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); } } } } - private void validatecompany(int rowNum, String companyName, Long deptId){ - //根据单位名称获取单位id - Long companyId = null ; + private void validatecompany(int rowNum, String companyName, Long companyId, Long deptId){ // 校验数组长度是否一致 if (companyId == null) { - throw new IllegalArgumentException(String.format( + throw exception0(BAD_REQUEST.getCode(), String.format( "第%d条数据:单位名称不正确(%s)", rowNum, companyName )); } // 校验导入单位 - if (deptId != 100 && !companyId.equals(deptId)) { - throw new IllegalArgumentException(String.format( + if (deptId != 100 && !deptId.equals(companyId)) { + throw exception0(BAD_REQUEST.getCode(), String.format( "第%d条数据:导入单位%s与当前操作员单位不一致", rowNum, companyName )); @@ -451,7 +509,7 @@ public class CostFeeServiceImpl implements CostFeeService { // 重复校验:如果Set中已存在该团队名称,说明重复 if (checkedTeams.contains(trimmedTeamName)) { - throw new IllegalArgumentException(String.format( + throw exception0(BAD_REQUEST.getCode(), String.format( "第%d条数据:%s名称重复", rowNum, teamName )); @@ -459,7 +517,7 @@ public class CostFeeServiceImpl implements CostFeeService { // 存在性校验:检查团队是否在dept中存在 if (!dept.contains(trimmedTeamName)) { - throw new IllegalArgumentException(String.format( + throw exception0(BAD_REQUEST.getCode(), String.format( "第%d条数据:%s名称不正确", rowNum, teamName )); @@ -469,4 +527,15 @@ public class CostFeeServiceImpl implements CostFeeService { checkedTeams.add(trimmedTeamName); } } + + //查询单位下的班组信息 + private Map> getDetpList (List deptIds) { + Map> listMap = new HashMap<>(); + for (Long depId : deptIds) { + List deptList = deptApi.getChildDeptList(depId); + List names = deptList.stream().map(DeptRespDTO::getName).collect(Collectors.toList()); + listMap.put(depId,names); + } + return listMap; + } } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApi.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApi.java index edd3318..f6265eb 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApi.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApi.java @@ -58,4 +58,11 @@ public interface DeptApi { */ List getChildDeptList(Long id); + /** + * 获取二级部门 + * + * @return 子部门列表 + */ + List getSubDeptList(); + } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApiImpl.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApiImpl.java index 4947028..a784566 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApiImpl.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/api/dept/DeptApiImpl.java @@ -2,6 +2,7 @@ package com.zteits.urbanops.module.system.api.dept; import com.zteits.urbanops.framework.common.util.object.BeanUtils; import com.zteits.urbanops.module.system.api.dept.dto.DeptRespDTO; +import com.zteits.urbanops.module.system.controller.admin.dept.vo.dept.DeptRespVO; import com.zteits.urbanops.module.system.dal.dataobject.dept.DeptDO; import com.zteits.urbanops.module.system.service.dept.DeptService; import org.springframework.stereotype.Service; @@ -10,6 +11,8 @@ import jakarta.annotation.Resource; import java.util.Collection; import java.util.List; +import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; + /** * 部门 API 实现类 * @@ -44,4 +47,10 @@ public class DeptApiImpl implements DeptApi { return BeanUtils.toBean(childDeptList, DeptRespDTO.class); } + @Override + public List getSubDeptList() { + List list = deptService.getSubDeptList(); + return BeanUtils.toBean(list, DeptRespDTO.class); + } + } diff --git a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/dept/DeptController.java b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/dept/DeptController.java index 784ebdc..32c00c3 100644 --- a/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/dept/DeptController.java +++ b/urbanops-module-system/src/main/java/com/zteits/urbanops/module/system/controller/admin/dept/DeptController.java @@ -1,3 +1,4 @@ + package com.zteits.urbanops.module.system.controller.admin.dept; import com.zteits.urbanops.framework.common.enums.CommonStatusEnum; -- libgit2 0.21.4