diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevMergeController.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevMergeController.java index 2101b6f..895b1cf 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevMergeController.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/controller/DevMergeController.java @@ -11,6 +11,7 @@ import com.bonus.common.core.web.page.TableDataInfo; import com.bonus.common.log.annotation.SysLog; import com.bonus.common.log.enums.OperaType; import com.bonus.common.security.utils.SecurityUtils; +import com.bonus.material.devConfig.domain.EquipmentProperty; import com.bonus.material.devchange.domain.MaDevInfo; import com.bonus.material.devchange.domain.MaDevInfoXlsx; import com.bonus.material.device.domain.DevInfo; @@ -22,9 +23,16 @@ import com.bonus.material.device.service.DevInfoService; import com.bonus.material.device.service.DevMergeService; import com.bonus.material.utils.CenterExcelUtil; import com.bonus.material.utils.FolderZipUtil; +import com.bonus.material.utils.MultiSheetExcelUtil; import com.bonus.material.utils.ReflectUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; +import lombok.extern.slf4j.Slf4j; +import org.apache.poi.ss.usermodel.*; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; + +import java.io.ByteArrayOutputStream; +import java.io.InputStream; import org.apache.commons.lang3.ObjectUtils; import org.apache.poi.ss.usermodel.Sheet; import org.springframework.web.bind.annotation.*; @@ -46,6 +54,7 @@ import java.util.Map; * * @author syruan */ +@Slf4j @RestController @RequestMapping("/order") @Api(value = "设备信息", tags = "设备管理") @@ -282,8 +291,11 @@ public class DevMergeController extends BaseController { @SysLog(title = "用户管理", businessType = OperaType.IMPORT, logType = 0, module = "系统管理->用户管理", details = "导入用户信息") public AjaxResult importData(MultipartFile file, String orderId) throws Exception { try { - ExcelUtil util = new ExcelUtil(EquipmentImportDTO.class); - List list = util.importExcel(file.getInputStream(), 1); + // 先计算Excel中的公式,将公式结果转换为文本值 + MultipartFile processedFile = processExcelFormulas(file); + + ExcelUtil util = new ExcelUtil<>(EquipmentImportDTO.class); + List list = util.importExcel(processedFile.getInputStream(), 0); return service.importData(list, orderId); } catch (Exception e) { logger.error(e.toString(), e); @@ -292,6 +304,85 @@ public class DevMergeController extends BaseController { } + /** + * 处理Excel中的公式,将公式计算结果转换为文本值 + * 这样ExcelUtil就能正确读取公式单元格的值 + */ + private MultipartFile processExcelFormulas(MultipartFile file) throws Exception { + try (InputStream inputStream = file.getInputStream(); + Workbook workbook = new XSSFWorkbook(inputStream)) { + + // 创建公式计算器 + FormulaEvaluator evaluator = workbook.getCreationHelper().createFormulaEvaluator(); + + // 获取第一个Sheet(装备信息) + Sheet sheet = workbook.getSheetAt(0); + + // 遍历所有行 + for (Row row : sheet) { + if (row == null) continue; + + // 遍历所有单元格 + for (Cell cell : row) { + if (cell == null) continue; + + // 如果是公式单元格,计算公式并将结果转换为文本值 + if (cell.getCellType() == CellType.FORMULA) { + try { + // 计算公式 + CellValue cellValue = evaluator.evaluate(cell); + + // 根据计算结果类型,将公式转换为对应的值 + switch (cellValue.getCellType()) { + case STRING: + String stringValue = cellValue.getStringValue(); + cell.setCellFormula(null); // 移除公式 + cell.setCellValue(stringValue); // 设置为文本值 + break; + case NUMERIC: + double numericValue = cellValue.getNumberValue(); + cell.setCellFormula(null); + cell.setCellValue(numericValue); + break; + case BOOLEAN: + boolean booleanValue = cellValue.getBooleanValue(); + cell.setCellFormula(null); + cell.setCellValue(booleanValue); + break; + case BLANK: + case ERROR: + default: + // 空值或错误,转换为空字符串 + cell.setCellFormula(null); + cell.setCellValue(""); + break; + } + } catch (Exception e) { + // 公式计算失败,设置为空字符串 + logger.warn("公式计算失败,单元格位置:行{} 列{},错误:{}", + row.getRowNum(), cell.getColumnIndex(), e.getMessage()); + cell.setCellFormula(null); + cell.setCellValue(""); + } + } + } + } + + // 将处理后的Workbook写入到字节数组 + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + workbook.write(baos); + byte[] bytes = baos.toByteArray(); + + // 创建新的MultipartFile对象 + return new org.springframework.mock.web.MockMultipartFile( + file.getName(), + file.getOriginalFilename(), + file.getContentType(), + bytes + ); + } + } + /** * 下载指定空文件夹结构的 ZIP 包 @@ -317,39 +408,37 @@ public class DevMergeController extends BaseController { } /** - * 下载导入模板 + * 下载导入模板(带特征项联动功能) */ @PostMapping("/template") public void downloadTemplate(HttpServletResponse response) { - // 1. 查询系统中的专业列表(核心:动态获取下拉选数据) - List professionList = service.listAllProfessionNames(); - // 转换为数组(工具类需要String[]类型) - String[] professionArray = professionList.toArray(new String[0]); - - // 1.2 生产厂家列表 - List manufacturerList = service.listAllManufacturerNames(); - String[] manufacturerArray = manufacturerList.toArray(new String[0]); - - // 2. 反射修改@Excel注解的combo属性(关键:动态注入下拉选值) try { - Field professionField = EquipmentImportDTO.class.getDeclaredField("profession"); - Excel excelAnnotation = professionField.getAnnotation(Excel.class); - // 通过反射修改注解的combo属性(需借助自定义的注解修改工具,或直接复用工具类逻辑) - // 方案1:使用反射工具修改注解属性(推荐) - ReflectUtils.setAnnotationValue(excelAnnotation, "combo", professionArray); + // 1. 查询所有装备类型及其特征项 + Map> typePropertiesMap = service.getAllTypePropertiesMap(); - Field manufacturerField = EquipmentImportDTO.class.getDeclaredField("manufacturer"); - Excel manufacturerExcel = manufacturerField.getAnnotation(Excel.class); - ReflectUtils.setAnnotationValue(manufacturerExcel, "combo", manufacturerArray); + // 2. 查询装备类型列表(用于下拉框) + List professionList = service.listAllProfessionNames(); + String[] professionArray = professionList.toArray(new String[0]); + // 3. 查询生产厂家列表(用于下拉框) + List manufacturerList = service.listAllManufacturerNames(); + String[] manufacturerArray = manufacturerList.toArray(new String[0]); + + // 4. 计数单位列表(用于下拉框) + String[] unitArray = new String[]{"台", "套", "个", "辆", "件"}; + + // 5. 使用多Sheet工具类生成模板 + MultiSheetExcelUtil.generateMultiSheetTemplate( + response, + "装备信息导入模板", + typePropertiesMap, + professionArray, + manufacturerArray, + unitArray + ); } catch (Exception e) { - throw new RuntimeException("设置专业下拉选失败", e); + log.error("生成模板失败", e); + throw new RuntimeException("生成模板失败: " + e.getMessage(), e); } - - // 3. 调用工具类生成模板(自动触发下拉选生成) - // 使用 CenterExcelUtil 替代 ExcelUtil,确保所有单元格(包括空白行)都居中对齐 - CenterExcelUtil util = new CenterExcelUtil<>(EquipmentImportDTO.class); - // importTemplateExcel:专门生成导入模板的方法(仅表头+下拉选,无数据) - util.importTemplateExcel(response, "装备信息导入模板", "装备信息导入模板"); } } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/EquipmentImportDTO.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/EquipmentImportDTO.java index c0d87e3..498292a 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/EquipmentImportDTO.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/domain/EquipmentImportDTO.java @@ -56,5 +56,61 @@ public class EquipmentImportDTO { @Excel(name = "采购日期", sort = 11, dateFormat = "yyyy-MM-dd", align = HorizontalAlignment.CENTER) @JsonFormat(pattern = "yyyy-MM-dd") private Date purchaseDate; + + // ==================== 特征项(9组,每组2列:特征项名称 + 特征值) ==================== + + @Excel(name = "特征项1", sort = 12, align = HorizontalAlignment.CENTER) + private String propertyName1; + + @Excel(name = "特征值1", sort = 13, align = HorizontalAlignment.CENTER) + private String propertyValue1; + + @Excel(name = "特征项2", sort = 14, align = HorizontalAlignment.CENTER) + private String propertyName2; + + @Excel(name = "特征值2", sort = 15, align = HorizontalAlignment.CENTER) + private String propertyValue2; + + @Excel(name = "特征项3", sort = 16, align = HorizontalAlignment.CENTER) + private String propertyName3; + + @Excel(name = "特征值3", sort = 17, align = HorizontalAlignment.CENTER) + private String propertyValue3; + + @Excel(name = "特征项4", sort = 18, align = HorizontalAlignment.CENTER) + private String propertyName4; + + @Excel(name = "特征值4", sort = 19, align = HorizontalAlignment.CENTER) + private String propertyValue4; + + @Excel(name = "特征项5", sort = 20, align = HorizontalAlignment.CENTER) + private String propertyName5; + + @Excel(name = "特征值5", sort = 21, align = HorizontalAlignment.CENTER) + private String propertyValue5; + + @Excel(name = "特征项6", sort = 22, align = HorizontalAlignment.CENTER) + private String propertyName6; + + @Excel(name = "特征值6", sort = 23, align = HorizontalAlignment.CENTER) + private String propertyValue6; + + @Excel(name = "特征项7", sort = 24, align = HorizontalAlignment.CENTER) + private String propertyName7; + + @Excel(name = "特征值7", sort = 25, align = HorizontalAlignment.CENTER) + private String propertyValue7; + + @Excel(name = "特征项8", sort = 26, align = HorizontalAlignment.CENTER) + private String propertyName8; + + @Excel(name = "特征值8", sort = 27, align = HorizontalAlignment.CENTER) + private String propertyValue8; + + @Excel(name = "特征项9", sort = 28, align = HorizontalAlignment.CENTER) + private String propertyName9; + + @Excel(name = "特征值9", sort = 29, align = HorizontalAlignment.CENTER) + private String propertyValue9; } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevMergeService.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevMergeService.java index 495c48d..3eff0a8 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevMergeService.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/DevMergeService.java @@ -5,6 +5,7 @@ import com.bonus.common.biz.domain.BmCompanyInfo; import com.bonus.common.core.web.domain.AjaxResult; import com.bonus.material.devchange.domain.MaDevInfo; import com.bonus.material.devchange.domain.MaDevInfoXlsx; +import com.bonus.material.devConfig.domain.EquipmentProperty; import com.bonus.material.device.domain.DevInfo; import com.bonus.material.device.domain.EquipmentImportDTO; import com.bonus.material.device.domain.dto.DevInfoImpDto; @@ -81,4 +82,10 @@ public interface DevMergeService { void downloadEmptyFolderZip(HttpServletResponse response,String orderId); AjaxResult uploadAndUnzipMultiDeviceZip(MultipartFile zipFile,String orderId); + + /** + * 查询所有装备类型及其特征项 + * @return Map<装备类型名称, 特征项列表> + */ + Map> getAllTypePropertiesMap(); } diff --git a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevMergeServiceImpl.java b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevMergeServiceImpl.java index f8d59da..1bb077a 100644 --- a/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevMergeServiceImpl.java +++ b/bonus-modules/bonus-material-mall/src/main/java/com/bonus/material/device/service/impl/DevMergeServiceImpl.java @@ -46,6 +46,7 @@ import java.text.SimpleDateFormat; import java.time.LocalDate; import java.time.format.DateTimeFormatter; import java.util.*; +import java.util.stream.Collectors; import java.util.zip.ZipEntry; import java.util.zip.ZipException; import java.util.zip.ZipInputStream; @@ -349,8 +350,13 @@ public class DevMergeServiceImpl implements DevMergeService { public List getDeviceByOrderId(MaDevInfo o) { List list = devMergeMapper.getDeviceByOrderId(o); list.forEach(item -> { + log.info("查询装备特征项:maId={}", item.getMaId()); List propertiiesList = mapper.getProperties(item); + log.info("查询到的特征项数量:{}", propertiiesList != null ? propertiiesList.size() : 0); if (propertiiesList != null && !propertiiesList.isEmpty()) { + propertiiesList.forEach(prop -> { + log.info("特征项:name={}, value={}", prop.getPropertyName(), prop.getPropertyValue()); + }); item.setPropertyVoList(propertiiesList); } item.setAppearanceImages(mapper.getFileList(item.getMaId(), 1)); @@ -431,10 +437,20 @@ public class DevMergeServiceImpl implements DevMergeService { @Transactional(rollbackFor = Exception.class) // 添加事务,确保数据一致性 @Override public AjaxResult importData(List list, String orderId) { - // 1. 基础校验 - if (ObjectUtil.isEmpty(list) || list.isEmpty()) { + // 1. 基础校验和过滤null元素(Excel空行会被解析为null) + if (list == null || list.isEmpty()) { return AjaxResult.error("表格内没有数据"); } + + // 2. 过滤null元素 + list = list.stream().filter(Objects::nonNull).collect(Collectors.toList()); + + // 2.1 过滤装备类目都没选择的数据 + list = list.stream().filter(item -> StringUtils.isNotBlank(item.getProfession())).collect(Collectors.toList()); + + // 3. 再次检查过滤后是否有有效数据 + if (list.isEmpty()) {return AjaxResult.error("表格内没有有效数据");} + if (ObjectUtil.isEmpty(orderId)) { DevMergeVo o = new DevMergeVo(); o.setCreateUser(SecurityUtils.getLoginUser().getSysUser().getNickName()); @@ -461,7 +477,6 @@ public class DevMergeServiceImpl implements DevMergeService { return AjaxResult.error("导入失败,存在以下错误:" + String.join(" ", errorMessages)); } - // 4. 遍历数据,进行最终校验和导入 for (int i = 0; i < list.size(); i++) { EquipmentImportDTO item = list.get(i); @@ -501,18 +516,50 @@ public class DevMergeServiceImpl implements DevMergeService { Integer insertResult = devMergeMapper.interDevice(maDevInfo); devInfoMapper.deleteDevInfoProperties(Long.valueOf(maDevInfo.getMaId())); - List equipmentProperties = equipmentPropertyMapper.selectByType(Long.valueOf(typeId)); - if (equipmentProperties != null && !equipmentProperties.isEmpty()) { - List devInfoPropertyVos = new ArrayList<>(); - for (EquipmentProperty equipmentProperty : equipmentProperties) { - DevInfoPropertyVo entity = new DevInfoPropertyVo(); - entity.setId(Math.toIntExact(equipmentProperty.getId())); - entity.setPropertyName(equipmentProperty.getPropertyName()); - entity.setPropertyValue(null); - devInfoPropertyVos.add(entity); - } - devInfoMapper.insertDevInfoProperties(Long.valueOf(maDevInfo.getMaId()), devInfoPropertyVos); + // 处理特征项数据 + List devInfoPropertyVos = new ArrayList<>(); + + // 从Excel导入的9组特征项数据中提取有效数据 + String[] propertyNames = { + item.getPropertyName1(), item.getPropertyName2(), item.getPropertyName3(), + item.getPropertyName4(), item.getPropertyName5(), item.getPropertyName6(), + item.getPropertyName7(), item.getPropertyName8(), item.getPropertyName9() + }; + String[] propertyValues = { + item.getPropertyValue1(), item.getPropertyValue2(), item.getPropertyValue3(), + item.getPropertyValue4(), item.getPropertyValue5(), item.getPropertyValue6(), + item.getPropertyValue7(), item.getPropertyValue8(), item.getPropertyValue9() + }; + + // 遍历9组特征项,只保存有效的(特征项名称和特征值都不为空) + for (int j = 0; j < 9; j++) { + String propertyName = propertyNames[j]; + String propertyValue = propertyValues[j]; + + log.info("第{}行,特征项{}:name={}, value={}", i + 1, j + 1, propertyName, propertyValue); + + // 只有当特征项名称和特征值都不为空时才保存 + if (propertyName != null && !propertyName.trim().isEmpty() && propertyValue != null && !propertyValue.trim().isEmpty()) { + + DevInfoPropertyVo entity = new DevInfoPropertyVo(); + entity.setPropertyName(propertyName.trim()); + entity.setPropertyValue(propertyValue.trim()); + // 注意:这里不设置id,不需要关联ma_type_properties表的id + devInfoPropertyVos.add(entity); + log.info("第{}行,特征项{}有效,已添加到列表", i + 1, j + 1); + } + } + + log.info("第{}行,共提取到{}个有效特征项", i + 1, devInfoPropertyVos.size()); + + // 如果有特征项数据,则保存 + if (!devInfoPropertyVos.isEmpty()) { + log.info("开始保存特征项:maId={}, 数量={}", maDevInfo.getMaId(), devInfoPropertyVos.size()); + devInfoMapper.insertDevInfoProperties(Long.valueOf(maDevInfo.getMaId()), devInfoPropertyVos); + log.info("特征项保存成功"); + } else { + log.info("第{}行没有有效的特征项数据,跳过保存", i + 1); } if (insertResult > 0) { @@ -521,15 +568,21 @@ public class DevMergeServiceImpl implements DevMergeService { maDevQc.setQcCode(maDevInfo.getCode()); maDevQc.setCreateBy(String.valueOf(SecurityUtils.getUserId())); maDevQc.setCreateTime(DateUtils.getNowDate()); - maDevQc.setQcCom(Optional.ofNullable(SecurityUtils.getLoginUser().getSysUser().getCompanyId()) - .orElse(SecurityUtils.getLoginUser().getSysUser().getDeptId()).toString()); + maDevQc.setQcCom(Optional.ofNullable(SecurityUtils.getLoginUser().getSysUser().getCompanyId()).orElse(SecurityUtils.getLoginUser().getSysUser().getDeptId()).toString()); maDevQc.setNextCheckTime(maDevInfo.getNextMaintenanceDate()); qcMapper.insertDevQc(maDevQc); - devMergeMapper.insertOrderDevReal(orderId, Long.valueOf(maDevInfo.getMaId())); + // 关联订单(仅当orderId有效时) + if (orderId != null && !orderId.trim().isEmpty() && !"undefined".equalsIgnoreCase(orderId.trim()) + && !"null".equalsIgnoreCase(orderId.trim())) { + try { + devMergeMapper.insertOrderDevReal(orderId, Long.valueOf(maDevInfo.getMaId())); + } catch (NumberFormatException e) { + log.warn("订单ID格式无效,跳过关联订单:orderId={}", orderId); + } + } } } catch (Exception e) { - // 记录导入异常,方便排查 log.error("导入第{}行数据失败:{}", i + 1, e.getMessage(), e); errorMessages.add("第" + (i + 1) + "行导入失败:系统错误"); } @@ -1066,4 +1119,29 @@ public class DevMergeServiceImpl implements DevMergeService { throw new IllegalArgumentException("不支持的子目录:" + subFolder); } } + + @Override + public Map> getAllTypePropertiesMap() { + // 1. 查询所有装备类型名称 + List professionList = devMergeMapper.listAllProfessionNames(); + + // 2. 为每个装备类型查询其特征项 + Map> typePropertiesMap = new LinkedHashMap<>(); + + for (String profession : professionList) { + // 根据装备类型名称获取typeId + Integer typeId = devMergeMapper.getTypeId(profession); + + if (typeId != null) { + // 查询该类型的特征项 + List properties = equipmentPropertyMapper.selectByTypeId(typeId.longValue()); + typePropertiesMap.put(profession, properties != null ? properties : new ArrayList<>()); + } else { + // 如果没有typeId,放入空列表 + typePropertiesMap.put(profession, new ArrayList<>()); + } + } + + return typePropertiesMap; + } }