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