diff --git a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java index 2f8a19c..84dfded 100644 --- a/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java +++ b/urbanops-framework/urbanops-spring-boot-starter-excel/src/main/java/com/zteits/urbanops/framework/excel/core/util/ExcelUtils.java @@ -53,6 +53,7 @@ public class ExcelUtils { return FastExcelFactory.read(file.getInputStream(), head, null) .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 .headRowNumber(startIndex) - .doReadAllSync(); + .sheet(0) + .doReadSync(); } } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java index 7b6094e..c4ef24d 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/controller/admin/costfee/CostFeeController.java @@ -6,6 +6,7 @@ import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.WaterFeeImp import com.zteits.urbanops.module.garden.enums.TemplateEnum; import com.zteits.urbanops.module.garden.service.costfee.CostFeeService; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.servlet.http.HttpServletResponse; import lombok.AllArgsConstructor; import lombok.Data; @@ -32,6 +33,7 @@ import static com.zteits.urbanops.framework.common.pojo.CommonResult.success; * 人机材公共处理控制器 * 处理Excel导入预览、提交等功能 */ +@Tag(name = "管理后台 - 人机材批量导入") @RestController @RequestMapping("/costfee") @Slf4j @@ -100,6 +102,7 @@ public class CostFeeController{ * Excel文件上传预览 */ @PostMapping("/preview") + @Operation(summary = "文件上传") public CommonResult previewExcel(@RequestParam("file") MultipartFile file, @RequestParam("templateName") String templateName) { // 基础参数校验 @@ -145,6 +148,7 @@ public class CostFeeController{ * 分页获取预览数据 */ @GetMapping("/preview-data") + @Operation(summary = "批量上传分页预览") public CommonResult getPreviewData(@RequestParam("batchId") String batchId, @RequestParam("templateName") String templateName, @RequestParam(value = "page", defaultValue = "1") int page, @@ -186,6 +190,7 @@ public class CostFeeController{ * 提交导入数据到数据库 */ @PostMapping("/submit") + @Operation(summary = "批次保存") public CommonResult submitImport(@RequestParam("batchId") String batchId, @RequestParam("templateName") String templateName, @RequestParam("operator") String operator) { diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/departmentcost/DepartmentCostMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/departmentcost/DepartmentCostMapper.java index de8b67c..8f5c1cc 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/departmentcost/DepartmentCostMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/departmentcost/DepartmentCostMapper.java @@ -31,5 +31,12 @@ public interface DepartmentCostMapper extends BaseMapperX { .betweenIfPresent(DepartmentCostDO::getCreateTime, reqVO.getCreateTime()) .orderByDesc(DepartmentCostDO::getId)); } + /** + * 批量查询重复数据 + * + * @param keyList 部门信息集合 + * @return 结果 + */ + public List selectDuplicateIds(List keyList); -} \ No newline at end of file +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/mechanicalcost/MechanicalCostMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/mechanicalcost/MechanicalCostMapper.java index 5106799..53bfdf0 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/mechanicalcost/MechanicalCostMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/mechanicalcost/MechanicalCostMapper.java @@ -39,4 +39,5 @@ public interface MechanicalCostMapper extends BaseMapperX { .orderByDesc(MechanicalCostDO::getId)); } -} \ No newline at end of file + public List selectDuplicateIds(List keyList); +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/personalcost/PersonalCostMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/personalcost/PersonalCostMapper.java index 155b9d2..16baa86 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/personalcost/PersonalCostMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/personalcost/PersonalCostMapper.java @@ -32,4 +32,13 @@ public interface PersonalCostMapper extends BaseMapperX { .orderByDesc(PersonalCostDO::getId)); } -} \ No newline at end of file + + /** + * 批量查询重复数据 + * + * @param keyList 人员费用信息集合 + * @return 结果 + */ + public List selectDuplicateIds(List keyList); + +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/waterfee/WaterFeeMapper.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/waterfee/WaterFeeMapper.java index b89e5e0..7a6e382 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/waterfee/WaterFeeMapper.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/dal/mysql/waterfee/WaterFeeMapper.java @@ -32,4 +32,12 @@ public interface WaterFeeMapper extends BaseMapperX { .orderByDesc(WaterFeeDO::getId)); } -} \ No newline at end of file + + /** + * 批量查询重复数据 + * + * @param keyList 水费录入信息集合 + * @return 结果 + */ + public List selectDuplicateIds(List keyList); +} diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/TemplateEnum.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/TemplateEnum.java index 08c01b2..d11a53e 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/TemplateEnum.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/enums/TemplateEnum.java @@ -7,6 +7,9 @@ import lombok.Getter; import java.util.HashMap; import java.util.Map; +import static com.zteits.urbanops.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST; +import static com.zteits.urbanops.framework.common.exception.util.ServiceExceptionUtil.exception0; + /** * Excel导入模板枚举 * 存储模板编码、名称、起始行索引及对应的数据导入类 @@ -29,35 +32,35 @@ public enum TemplateEnum { 2, PersonalCostImport.class, true // 新增默认值 + ), + COSTFEE_DEPARTMENT( + "costFee_department", + "综合费用导入模板.xlsx", + 2, + DepartmentCostImport.class, + true // 新增默认值 + ), + COSTFEE_GARDENWASTE( + "costFee_gardenWaste", + "园林废弃物费用导入模板.xlsx", + 2, + GardenWasteRecordImport.class, + true // 新增默认值 + ), + COSTFEE_MECHANICAL( + "costFee_mechanical", + "自有机械费用导入模板.xlsx", + 2, + MechanicalCostImport.class, + true // 新增默认值 + ), + COSTFEE_RENTALMECHANICAL( + "costFee_rentalmechanical", + "租赁机械费用导入模板.xlsx", + 2, + RentalMechanicalCostImport.class, + true // 新增默认值 ); -// COSTFEE_DEPARTMENT( -// "costFee_department", -// "综合费用导入模板.xlsx", -// 1, -// DepartmentCostImport.class, -// true // 新增默认值 -// ), -// COSTFEE_GARDENWASTE( -// "costFee_gardenWaste", -// "园林废弃物费用导入模板.xlsx", -// 1, -// GardenWasteRecordImport.class, -// true // 新增默认值 -// ), -// COSTFEE_MECHANICAL( -// "costFee_mechanical", -// "自有机械费用导入模板.xlsx", -// 1, -// MechanicalCostImport.class, -// true // 新增默认值 -// ), -// COSTFEE_RENTALMECHANICAL( -// "costFee_rentalmechanical", -// "租赁机械费用导入模板.xlsx", -// 1, -// RentalMechanicalCostImport.class, -// true // 新增默认值 -// ); // 模板编码(前端传递的标识) private final String code; @@ -86,12 +89,12 @@ public enum TemplateEnum { public static TemplateEnum getByCode(String code) { // 空值校验 if (code == null || code.trim().isEmpty()) { - throw new RuntimeException("模板编码不能为空"); + throw exception0(BAD_REQUEST.getCode(),"模板编码不能为空"); } // 从缓存获取,避免循环遍历 TemplateEnum template = CODE_MAP.get(code); if (template == null) { - throw new RuntimeException("无效的模板编码:" + code); + throw exception0(BAD_REQUEST.getCode(),"无效的模板编码"+code); } return template; } diff --git a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java index bea389e..4071e1f 100644 --- a/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java +++ b/urbanops-module-garden/src/main/java/com/zteits/urbanops/module/garden/service/costfee/CostFeeServiceImpl.java @@ -5,6 +5,18 @@ import com.fasterxml.jackson.databind.ObjectMapper; import com.zteits.urbanops.framework.security.core.LoginUser; import com.zteits.urbanops.module.garden.controller.admin.costfee.CostFeeController; import com.zteits.urbanops.module.garden.controller.admin.costfee.vo.*; +import com.zteits.urbanops.module.garden.dal.dataobject.departmentcost.DepartmentCostDO; +import com.zteits.urbanops.module.garden.dal.dataobject.mechanicalcost.MechanicalCostDO; +import com.zteits.urbanops.module.garden.dal.dataobject.personalcost.PersonalCostDO; +import com.zteits.urbanops.module.garden.dal.dataobject.rentalmechanicalcost.RentalMechanicalCostDO; +import com.zteits.urbanops.module.garden.dal.dataobject.wasterecord.WasteRecordDO; +import com.zteits.urbanops.module.garden.dal.dataobject.waterfee.WaterFeeDO; +import com.zteits.urbanops.module.garden.dal.mysql.departmentcost.DepartmentCostMapper; +import com.zteits.urbanops.module.garden.dal.mysql.mechanicalcost.MechanicalCostMapper; +import com.zteits.urbanops.module.garden.dal.mysql.personalcost.PersonalCostMapper; +import com.zteits.urbanops.module.garden.dal.mysql.rentalmechanicalcost.RentalMechanicalCostMapper; +import com.zteits.urbanops.module.garden.dal.mysql.wasterecord.WasteRecordMapper; +import com.zteits.urbanops.module.garden.dal.mysql.waterfee.WaterFeeMapper; import com.zteits.urbanops.module.garden.enums.AssignTasksConstants; import com.zteits.urbanops.module.garden.enums.CostFeeConstants; import com.zteits.urbanops.module.garden.enums.TemplateEnum; @@ -16,6 +28,7 @@ import jakarta.validation.ConstraintViolationException; import jakarta.validation.Validator; import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; +import org.springframework.beans.BeanUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.redis.core.BoundHashOperations; import org.springframework.data.redis.core.RedisTemplate; @@ -24,8 +37,10 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.util.CollectionUtils; import java.io.IOException; +import java.time.LocalDate; import java.util.*; import java.util.concurrent.TimeUnit; +import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.IntStream; @@ -59,6 +74,20 @@ public class CostFeeServiceImpl implements CostFeeService { @Resource private DeptApi deptApi; + @Autowired + private WaterFeeMapper waterFeeMapper; + @Autowired + private PersonalCostMapper personalCostMapper; + @Autowired + private DepartmentCostMapper departmentCostMapper; + @Autowired + private MechanicalCostMapper mechanicalCostMapper; + @Autowired + private RentalMechanicalCostMapper rentalMechanicalCostMapper; + @Autowired + private WasteRecordMapper wasteRecordMapper; + + @Override /** * 保存数据到Redis @@ -99,7 +128,7 @@ public class CostFeeServiceImpl implements CostFeeService { TemplateEnum template = TemplateEnum.getByCode(templateName); Class importClass = template.getImportClass(); if (importClass == null) { - throw exception(COST_FEE_NOT_EXISTS_001); + throw exception0(BAD_REQUEST.getCode(),"导入模板配置异常"); } // 分页参数设置 @@ -118,7 +147,7 @@ public class CostFeeServiceImpl implements CostFeeService { if (dataList == null || dataList.isEmpty()) { // 首次分页就无数据,抛出异常 if (pageNum == 1) { - throw new IllegalArgumentException("没有可导入的数据或数据已过期"); + throw exception0(BAD_REQUEST.getCode(),"没有可导入的数据或数据已过期"); } break; } @@ -128,7 +157,7 @@ public class CostFeeServiceImpl implements CostFeeService { // 调用业务服务执行本页数据导入 int currentPageCount = dataList.size(); - //insertData(template, dataList, operator); + insertData(template, dataList, operator); totalImported += currentPageCount; // 打印分页处理日志(可选) @@ -152,6 +181,243 @@ public class CostFeeServiceImpl implements CostFeeService { return totalImported; } + /** + * @Author wangqian + * @Description 保存数据 + * @Date 2025/7/20 11:30 + * @Param template + * @param dataList + * @param operator + * @Return void + */ + private void insertData(TemplateEnum template, List dataList, String operator) { + if (CollectionUtils.isEmpty(dataList)) { + log.info("数据列表为空,跳过插入操作"); + return; + } + //单位集合 + List companyList = deptApi.getSubDeptList(); + // 1. 预加载部门映射(循环外加载,避免重复查询) + List deptIds = companyList.stream().map(DeptRespDTO::getId).collect(Collectors.toList()); + Map nameToCompanyIdMap = CollectionUtils.isEmpty(companyList) ? Map.of() + : companyList.stream() + // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整) + .collect(Collectors.toMap( + dept -> dept.getName().trim(), // value 映射:取 companyName(去空格) + DeptRespDTO::getId, // key 映射:取 companyId + (oldValue, newValue) -> newValue + )); + + + // 模板类型校验 + Class importClass = template.getImportClass(); // 从枚举获取导入类,避免switch + // 根据不同模板类型执行不同的处理逻辑 + if (WaterFeeImport.class.isAssignableFrom(importClass)) { + Map companyIdToNamedMap = CollectionUtils.isEmpty(companyList) ? Map.of() + : companyList.stream() + // 转 Map:key=companyId,value=companyName;key 冲突时保留最后一个(可调整) + .collect(Collectors.toMap( + DeptRespDTO::getId, // key 映射:取 companyId + dept -> dept.getName().trim(), // value 映射:取 companyName(去空格) + (oldValue, newValue) -> newValue + )); + + // 处理水费数据 + List feeList = ((List) dataList).stream() + .map(importObj -> { + WaterFeeDO fee = new WaterFeeDO(); + BeanUtils.copyProperties(importObj, fee); + fee.setCompanyId(String.valueOf(getLoginUserDeptId())); + if (getLoginUserDeptId() == 100) { + fee.setCompanyName("蓟城山水"); + } else { + // 设置单位id + fee.setCompanyName(String.valueOf(companyIdToNamedMap.get(fee.getCompanyId()))); + } + return fee; + }) + .collect(Collectors.toMap( + // 生成唯一键:feeMonth + 分隔符 + meterCode(避免不同组合拼接后重复) + fee -> fee.getFeeMonth() + "_" + fee.getMeterCode(), + // 值为对象本身 + Function.identity(), + // 当键重复时,保留第一个出现的对象(可根据需求改为保留最后一个:(existing, replacement) -> replacement) + (existing, replacement) -> replacement + )) + .values() + .stream() + .collect(Collectors.toList()); + + if (!CollectionUtils.isEmpty(feeList)) { + //查询重复项,进行删除 + List ids = waterFeeMapper.selectDuplicateIds(feeList); + if (!CollectionUtils.isEmpty(ids)) { + waterFeeMapper.deleteByIds(ids); + } + // 调用批量插入方法 + waterFeeMapper.insertBatch(feeList); + } + log.info("成功插入 {} 条水费数据", feeList.size()); + } else if (PersonalCostImport.class.isAssignableFrom(importClass)) { + //去重 + dataList = personalFeeReplacement((List) dataList); + //获取班组对象 + Map teamIdMap = this.getDeptListAll(deptIds); + + // 3. 转换导入数据为PersonalCost列表(Stream优化+空指针防护) + List feeList = (List)dataList; + List saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size()); + if (!CollectionUtils.isEmpty(feeList)) { + for (PersonalCostImport importObj : feeList) { + PersonalCostDO fee = new PersonalCostDO(); + BeanUtils.copyProperties(importObj, fee); + // 设置单位id + fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName()))); + //设置班组id + fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds())); + saveFeeList.add(fee); + } + } + // 4. 处理重复数据(删除+插入原子性) + if (!feeList.isEmpty()) { + // 4.1 查询并删除重复项 + +// List duplicateIds = personalCostMapper.selectDuplicateIds(saveFeeList); +// if (!duplicateIds.isEmpty()) { +// personalCostMapper.deleteByIds(duplicateIds); +// } + + // 4.2 批量插入主表 + personalCostMapper.insertBatch(saveFeeList); + } + log.info("成功插入 {} 人员费用数据", dataList.size()); + } else if (DepartmentCostImport.class.isAssignableFrom(importClass)) { + //去重 + dataList = deptFeeReplacement((List)dataList); + List feeList = ((List) dataList).stream() + .map(importObj -> { + DepartmentCostDO fee = new DepartmentCostDO(); + BeanUtils.copyProperties(importObj, fee); + // 设置单位id + fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName()))); + return fee; + }) + .collect(Collectors.toList()); + if (!CollectionUtils.isEmpty(feeList)) { + //查询重复项,进行删除 + List ids = departmentCostMapper.selectDuplicateIds(feeList); + if (!CollectionUtils.isEmpty(ids)) { + departmentCostMapper.deleteByIds(ids); + } + // 调用批量插入方法 + departmentCostMapper.insertBatch(feeList); + } + log.info("成功插入 {} 综合费用数据", feeList.size()); + } else if (MechanicalCostImport.class.isAssignableFrom(importClass)) { + List feeList = (List) dataList; + List saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size()); + if (!CollectionUtils.isEmpty(feeList)) { + //获取班组对象 + Map teamIdMap = this.getDeptListAll(deptIds); + for (MechanicalCostImport importObj : feeList) { + MechanicalCostDO fee = new MechanicalCostDO(); + BeanUtils.copyProperties(importObj, fee); + // 设置单位id + fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName()))); + //设置班组id + fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds())); + // 3.3 计算结束时间(空指针防护) + Optional.ofNullable(fee.getStartUseTime()) + .ifPresent(startTime -> { + int useDuration = Optional.ofNullable(fee.getServiceLife()) + .map(Math::toIntExact) // 处理可能的溢出 + .orElse(0); + fee.setEndUseTime(this.getEndLocalDateForYears(startTime, useDuration)); + }); + + saveFeeList.add(fee); + } + } + // 4. 批量插入主表(空集合直接跳过) + if (!feeList.isEmpty()) { + //查询重复项,进行删除 + List ids = mechanicalCostMapper.selectDuplicateIds(saveFeeList); + if (!CollectionUtils.isEmpty(ids)) { + mechanicalCostMapper.deleteByIds(ids); + } + mechanicalCostMapper.insertBatch(saveFeeList); + } + log.info("成功插入 {} 自有机械费用数据", dataList.size()); + } else if (RentalMechanicalCostImport.class.isAssignableFrom(importClass)) { + List feeList = (List)dataList; + List saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size()); + if (!CollectionUtils.isEmpty(feeList)) { + //获取班组对象 + Map teamIdMap = this.getDeptListAll(deptIds); + for (RentalMechanicalCostImport importObj : feeList) { + RentalMechanicalCostDO fee = new RentalMechanicalCostDO(); + BeanUtils.copyProperties(importObj, fee); + // 设置单位id + fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName()))); + //设置班组id + List teamRels = new ArrayList<>(); + String teamIdsStr = StringUtils.defaultString(importObj.getTeamIds()).replace(",", ","); + String weightsStr = StringUtils.defaultString(importObj.getWeight()).replace(",", ","); + + String[] teamIds = teamIdsStr.split(","); + String[] weights = weightsStr.split(","); + for (int i = 0; i < teamIds.length; i++) { + TeamBizdomainRelVo rel = new TeamBizdomainRelVo(); + rel.setTeamName(teamIds[i].trim()); + // 提前处理部门ID映射(复用deptMap) + rel.setTeamId(Optional.ofNullable(teamIdMap.get(teamIds[i].trim())).map(String::valueOf).orElse(null)); + rel.setWeight(Long.valueOf(weights[i].trim())); + teamRels.add(rel); + } + fee.setTeamIds(teamRels); + + // 3.3 计算结束时间(空指针防护) + Optional.ofNullable(fee.getStartUseTime()) + .ifPresent(startTime -> { + int useDuration = Optional.ofNullable(fee.getUseDuration()) + .map(Math::toIntExact) // 处理可能的溢出 + .orElse(0); + fee.setEndUseTime(getEndLocalDateForYears(startTime, useDuration)); + }); + + saveFeeList.add(fee); + } + } + if (!feeList.isEmpty()) { + rentalMechanicalCostMapper.insertBatch(saveFeeList); + } + log.info("成功插入 {} 租赁机械费用数据", dataList.size()); + } else if (GardenWasteRecordImport.class.isAssignableFrom(importClass)) { + //获取班组对象 + List feeList = (List)dataList; + List saveFeeList = new ArrayList<>(CollectionUtils.isEmpty(feeList) ? 0 : feeList.size()); + if (!CollectionUtils.isEmpty(feeList)) { + Map teamIdMap = this.getDeptListAll(deptIds); + for (GardenWasteRecordImport importObj : feeList) { + WasteRecordDO fee = new WasteRecordDO(); + BeanUtils.copyProperties(importObj, fee); + // 设置单位id + fee.setCompanyId(String.valueOf(nameToCompanyIdMap.get(fee.getCompanyName()))); + //设置班组id + fee.setDeptId(getDeptList(teamIdMap, importObj.getTeamIds())); + + saveFeeList.add(fee); + } + } + // 4. 批量插入主表(若主表插入失败,事务回滚) + if (!feeList.isEmpty()) { + wasteRecordMapper.insertBatch(saveFeeList); + } + + log.info("成功插入 {} 园林废弃物费用数据", dataList.size()); + } + + } /** * 从Redis获取分页数据 @@ -309,6 +575,11 @@ public class CostFeeServiceImpl implements CostFeeService { // 抛出包含行号和所有错误的异常 // throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations); } + Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName()); + //单位校验 + validatecompany(rowNum, importBean.getCompanyName(), companyId, deptId); + //部门名称校验 + validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds()); } catch (ConstraintViolationException ex) { // 5. 包装异常,明确行号 + 真实校验错误( @@ -339,7 +610,11 @@ public class CostFeeServiceImpl implements CostFeeService { // 抛出包含行号和所有错误的异常 //throw exception0(BAD_REQUEST.getCode(), errorMsg.toString().trim(), violations); } - + Long companyId = nameToCompanyIdMap.get(importBean.getCompanyName()); + //单位校验 + validatecompany(rowNum, importBean.getCompanyName(), companyId, deptId); + //部门名称校验 + validateTeams(rowNum, deptList.get(companyId), importBean.getTeamIds()); } catch (ConstraintViolationException ex) { throw exception0(BAD_REQUEST.getCode(), String.format("第%d条数据校验失败:%s", rowNum, ex.getMessage()), ex); @@ -501,30 +776,32 @@ public class CostFeeServiceImpl implements CostFeeService { } } private void validateTeams(int rowNum, List dept, String teamIds) { - String[] teamNames = teamIds.split(","); - Set checkedTeams = new HashSet<>(); // 用于存储已校验的团队名称,检测重复 - - for (String teamName : teamNames) { - String trimmedTeamName = teamName.trim(); // 处理前后空格 + if (StringUtils.isNotEmpty(teamIds)) { + String[] teamNames = teamIds.split(","); + Set checkedTeams = new HashSet<>(); // 用于存储已校验的团队名称,检测重复 + + for (String teamName : teamNames) { + String trimmedTeamName = teamName.trim(); // 处理前后空格 + + // 重复校验:如果Set中已存在该团队名称,说明重复 + if (checkedTeams.contains(trimmedTeamName)) { + throw exception0(BAD_REQUEST.getCode(), String.format( + "第%d条数据:%s名称重复", + rowNum, teamName + )); + } - // 重复校验:如果Set中已存在该团队名称,说明重复 - if (checkedTeams.contains(trimmedTeamName)) { - throw exception0(BAD_REQUEST.getCode(), String.format( - "第%d条数据:%s名称重复", - rowNum, teamName - )); - } + // 存在性校验:检查团队是否在dept中存在 + if (!dept.contains(trimmedTeamName)) { + throw exception0(BAD_REQUEST.getCode(), String.format( + "第%d条数据:%s名称不正确", + rowNum, teamName + )); + } - // 存在性校验:检查团队是否在dept中存在 - if (!dept.contains(trimmedTeamName)) { - throw exception0(BAD_REQUEST.getCode(), String.format( - "第%d条数据:%s名称不正确", - rowNum, teamName - )); + // 将校验通过的团队名称加入Set,用于后续重复判断 + checkedTeams.add(trimmedTeamName); } - - // 将校验通过的团队名称加入Set,用于后续重复判断 - checkedTeams.add(trimmedTeamName); } } @@ -538,4 +815,167 @@ public class CostFeeServiceImpl implements CostFeeService { } return listMap; } + + /** + * 按salaryMonth、expenseName、companyId三个字段去重 + * @param costList 原始部门成本列表 + * @return 去重后的列表 + */ + public List deptFeeReplacement(List costList) { + if (costList == null || costList.isEmpty()) { + return new ArrayList<>(); + } + + // 1. 以三个字段的组合作为键(使用分隔符避免不同组合产生相同键) + // 2. 遇到重复键时保留第一条记录 + Map uniqueMap = costList.stream() + .collect(Collectors.toMap( + // 生成唯一键:salaryMonth|expenseName|companyId + cost -> buildUniqueKey(cost), + // 键不存在时存入当前对象 + cost -> cost, + // 键已存在时保留最后一个 + (existing, replacement) -> replacement + )); + + // 将去重后的结果转为List返回 + return new ArrayList<>(uniqueMap.values()); + } + + /** + * 构建三个字段的唯一组合键(处理null值) + */ + private String buildUniqueKey(DepartmentCostImport cost) { + // 使用Objects.toString处理可能的null值,避免NullPointerException + return Objects.toString(cost.getSalaryMonth(), "") + "|" + + Objects.toString(cost.getExpenseName(), "") + "|" + + Objects.toString(cost.getCompanyName(), ""); + } + + /** + * 按salaryMonth、name、companyId、teamIdList四个字段去重 + * @param costList 原始个人成本列表 + * @return 去重后的列表 + */ + public List personalFeeReplacement(List costList) { + if (costList == null || costList.isEmpty()) { + return new ArrayList<>(); + } + + // 1. 以四个字段的组合作为键(特别处理集合类型的teamIdList) + // 2. 遇到重复键时保留第一条记录 + Map uniqueMap = costList.stream() + .collect(Collectors.toMap( + // 生成唯一键:salaryMonth|name|companyId|teamIdListStr + this::buildUniqueKey, + // 键不存在时存入当前对象 + cost -> cost, + // 键已存在时保留第一个对象(可改为replacement保留最后一个) + (existing, replacement) -> replacement + )); + + // 将去重后的结果转为List返回 + return new ArrayList<>(uniqueMap.values()); + } + + /** + * 构建四个字段的唯一组合键(处理null值和集合类型) + */ + private String buildUniqueKey(PersonalCostImport cost) { + // 处理普通字段的null值 + String salaryMonth = Objects.toString(cost.getSalaryMonth(), ""); + String name = Objects.toString(cost.getName(), ""); + String companyId = Objects.toString(cost.getCompanyName(), ""); + String teamIds = Objects.toString(cost.getTeamIds(), ""); + // 拼接唯一键 + return salaryMonth + "|" + name + "|" + companyId + "|" + teamIds; + } + + + public static LocalDate getEndLocalDateForYears(LocalDate startDate, int years) { + if (startDate == null) { + throw new IllegalArgumentException("开始日期不能为null"); + } + if (years < 0) { + throw new IllegalArgumentException("年数不能为负数"); + } + return startDate.plusYears(years); + } + + /** + * @Author wangqian + * @Description 根据班组名称获取班组id + * @Date 2025/11/26 19:38 + */ + /** + * 根据部门ID列表获取「部门名称→部门ID」的映射(含空值防护) + */ + private Map getDeptListAll(List deptIds) { + Map deptNameMap = new HashMap<>(); + + // 1. 空值安全:避免 deptIds 为 null/空列表导致 NPE + if (CollectionUtils.isEmpty(deptIds)) { + return deptNameMap; + } + + for (Long deptId : deptIds) { + // 2. 跳过 null 的部门ID + if (deptId == null) { + continue; + } + + // 3. 获取子部门列表(防护接口返回 null) + List deptList = deptApi.getChildDeptList(deptId); + if (CollectionUtils.isEmpty(deptList)) { + continue; + } + + // 4. 遍历子部门:过滤无效名称,避免 Map 出现 null/空字符串键 + for (DeptRespDTO dept : deptList) { + if (dept == null) { + continue; + } + String deptName = dept.getName(); + // 过滤 null/空白名称(避免 Map 键为 null 或空字符串) + if (deptName == null || deptName.trim().isEmpty()) { + continue; + } + // 若名称重复,后遍历的部门会覆盖先遍历的(可根据业务调整规则) + deptNameMap.put(deptName.trim(), dept.getId()); + } + } + + return deptNameMap; + } + /** + * @Author wangqian + * @Description 根据班组名称获取班组id集合 + * @Date 2025/11/26 20:07 + */ + private List getDeptList(Map teamIdMap, String teamId) { + List list = new ArrayList<>(); + + // 1. 处理 teamId 为空的情况,直接返回空列表 + if (StringUtils.isEmpty(teamId)) { + return list; + } + + // 2. 分割字符串,处理空元素 + String[] teamArray = teamId.split(","); + for (String teamName : teamArray) { + // 3. 去除首尾空格,避免因空格导致匹配不到 + String trimTeamName = teamName.trim(); + if (StringUtils.isEmpty(trimTeamName)) { + continue; // 跳过空字符串元素 + } + + // 4. 从Map中获取ID,处理null值(避免添加null到列表) + Long deptId = teamIdMap.get(trimTeamName); + if (deptId != null) { + list.add(String.valueOf(deptId)); // 直接添加Long类型,修正原String类型错误 + } + } + + return list; + } } diff --git a/urbanops-module-garden/src/main/resources/mapper/departmentcost/DepartmentCostMapper.xml b/urbanops-module-garden/src/main/resources/mapper/departmentcost/DepartmentCostMapper.xml index 9cab3b3..4e33338 100644 --- a/urbanops-module-garden/src/main/resources/mapper/departmentcost/DepartmentCostMapper.xml +++ b/urbanops-module-garden/src/main/resources/mapper/departmentcost/DepartmentCostMapper.xml @@ -2,11 +2,13 @@ - + - \ No newline at end of file + diff --git a/urbanops-module-garden/src/main/resources/mapper/personalcost/PersonalCostMapper.xml b/urbanops-module-garden/src/main/resources/mapper/personalcost/PersonalCostMapper.xml index 34ac586..4379f5c 100644 --- a/urbanops-module-garden/src/main/resources/mapper/personalcost/PersonalCostMapper.xml +++ b/urbanops-module-garden/src/main/resources/mapper/personalcost/PersonalCostMapper.xml @@ -2,11 +2,12 @@ - - - \ No newline at end of file + + diff --git a/urbanops-module-garden/src/main/resources/mapper/waterfee/WaterFeeMapper.xml b/urbanops-module-garden/src/main/resources/mapper/waterfee/WaterFeeMapper.xml index 900ed2f..7102355 100644 --- a/urbanops-module-garden/src/main/resources/mapper/waterfee/WaterFeeMapper.xml +++ b/urbanops-module-garden/src/main/resources/mapper/waterfee/WaterFeeMapper.xml @@ -2,11 +2,14 @@ - - \ No newline at end of file + + + diff --git a/urbanops-module-gloabl/src/main/resources/mapper/mechanicalcost/MechanicalCostMapper.xml b/urbanops-module-gloabl/src/main/resources/mapper/mechanicalcost/MechanicalCostMapper.xml index 3116180..d2abe01 100644 --- a/urbanops-module-gloabl/src/main/resources/mapper/mechanicalcost/MechanicalCostMapper.xml +++ b/urbanops-module-gloabl/src/main/resources/mapper/mechanicalcost/MechanicalCostMapper.xml @@ -2,11 +2,13 @@ - + - \ No newline at end of file +