diff --git a/src/main/java/com/heai/modules/pms/controller/QcController.java b/src/main/java/com/heai/modules/pms/controller/QcController.java index 662dbf9..e3cc3df 100644 --- a/src/main/java/com/heai/modules/pms/controller/QcController.java +++ b/src/main/java/com/heai/modules/pms/controller/QcController.java @@ -164,6 +164,19 @@ public class QcController { return R.ok(); } + @PostMapping("/uploadMethodExcel") + public R uploadMethodExcel(@RequestParam(value = "file") MultipartFile file, @ModelAttribute GetParamInData data){ + qcService.uploadMethodExcel(file, data); + return R.ok(); + } + + @RequestMapping(value = "/downloadQcImportTemplate", method = {RequestMethod.GET, RequestMethod.POST}) + public void downloadQcImportTemplate(@RequestParam("flag") String flag, + @RequestParam(value = "site", required = false) String site, + HttpServletResponse response) { + qcService.downloadQcImportTemplate(flag, site, response); + } + // ======================= 根据物料设置检验模板API ======================= /** diff --git a/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java b/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java index 7aa056f..7105dc5 100644 --- a/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java +++ b/src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java @@ -18,6 +18,7 @@ import com.heai.modules.pms.mapper.QcDao; import com.heai.modules.pms.mapper.QcMapper; import com.heai.modules.pms.service.QcService; import com.heai.modules.pms.util.HttpClientUtil; +import com.heai.modules.pms.util.QcExcelImportHelper; import com.heai.modules.pms.util.ResponseData; import com.heai.modules.board.dao.BoardMapper; import com.heai.modules.production.dao.PrintLabelRecordMapper; @@ -36,6 +37,9 @@ import org.apache.commons.lang3.StringUtils; import org.apache.poi.ss.usermodel.CellType; import org.apache.poi.ss.usermodel.DataFormatter; import org.apache.poi.ss.usermodel.DateUtil; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.Workbook; import org.apache.poi.xssf.usermodel.XSSFCell; import org.apache.poi.xssf.usermodel.XSSFRow; import org.apache.poi.xssf.usermodel.XSSFSheet; @@ -53,10 +57,12 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; import javax.mail.internet.MimeMessage; +import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.FileOutputStream; import java.io.InputStream; import java.io.OutputStream; +import java.net.URLEncoder; import java.math.BigDecimal; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -324,82 +330,278 @@ public class QcServiceImpl implements QcService { @Override @Transactional public void taskUploadExcel(MultipartFile file, GetParamInData data) { - try{ - // 转流 - InputStream is = file.getInputStream(); - // 读取工作簿 - XSSFWorkbook workbook = new XSSFWorkbook(is); - // 读取工作表 - XSSFSheet sheet = workbook.getSheetAt(0); - // 获取行数 - int rows = sheet.getPhysicalNumberOfRows(); - // 声明对象 - QcItemData task = null; - // 遍历每一行(从第二行开始) - for (int j = 1; j < rows; j++) { - // 实例化对象 - task = new QcItemData(); - //获得该行 - XSSFRow row = sheet.getRow(j); - // 为对象赋值 - task.setSite(data.getSite()); - task.setInspectionTypeNo(row.getCell(3).getRawValue()); - // 获取编码 - String itemNo = qcMapper.getItemNo(task); - if ("101".equals(task.getInspectionTypeNo()) || "102".equals(task.getInspectionTypeNo()) || "103".equals(task.getInspectionTypeNo()) || "104".equals(task.getInspectionTypeNo())) { - task.setItemNo("IPQC-" + itemNo); - } else if ("105".equals(task.getInspectionTypeNo())) { - task.setItemNo("IQC-" + itemNo); - } else if ("106".equals(task.getInspectionTypeNo())) { - task.setItemNo("FAI-" + itemNo); - } else if ("107".equals(task.getInspectionTypeNo())) { - task.setItemNo("FQC-" + itemNo); - } else if ("108".equals(task.getInspectionTypeNo())) { - task.setItemNo("PQC-" + itemNo); - }else if("109".equals(task.getInspectionTypeNo())) { - data.setItemNo("SQC-" + itemNo); - }else if("110".equals(task.getInspectionTypeNo())) { - data.setItemNo("OQC-" + itemNo); + String site = QcExcelImportHelper.resolveSite(data.getSite()); + String buNo = QcExcelImportHelper.resolveBuNo(); + List types = loadInspectionTypes(site); + List programmes = loadSamplingProgrammes(site); + List levels = loadSamplingLevels(site); + List errors = new ArrayList<>(); + List toSave = new ArrayList<>(); + try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { + Sheet sheet = QcExcelImportHelper.getDataSheet(workbook, QcExcelImportHelper.FLAG_ITEM); + Map headers = QcExcelImportHelper.readHeaderIndex(sheet.getRow(0)); + int lastRow = sheet.getLastRowNum(); + for (int r = 1; r <= lastRow; r++) { + Row row = sheet.getRow(r); + if (QcExcelImportHelper.isBlankRow(row, headers)) { + continue; } - task.setItemDesc(row.getCell(0).getStringCellValue()); - task.setValueType(row.getCell(1).getStringCellValue()); - if (row.getCell(1).getStringCellValue().equals("文本")){ - task.setValueTypeDb("T"); - task.setMaxValue(null); - task.setMinValue(null); - } else if (row.getCell(1).getStringCellValue().equals("数字")) { - task.setValueTypeDb("N"); - if (row.getCell(3) == null) { - task.setMaxValue(null); - } else { - task.setMaxValue(BigDecimal.valueOf(row.getCell(3).getNumericCellValue())); + int excelRow = r + 1; + try { + QcItemData item = new QcItemData(); + item.setSite(site); + item.setBuNo(buNo); + item.setItemType("D"); + item.setCreatedBy(data.getCreateBy()); + item.setUpdateBy(data.getCreateBy()); + item.setItemDesc(QcExcelImportHelper.getString(row, headers, "项目名称")); + if (StringUtils.isBlank(item.getItemDesc())) { + throw new RuntimeException("项目名称不能为空"); } - if (row.getCell(4) == null) { - task.setMinValue(null); + String valueTypeDb = QcExcelImportHelper.resolveValueTypeDb(QcExcelImportHelper.getString(row, headers, "检验值类型")); + if (StringUtils.isBlank(valueTypeDb)) { + throw new RuntimeException("检验值类型不能为空"); + } + item.setValueTypeDb(valueTypeDb); + String typeValue = QcExcelImportHelper.getString(row, headers, "检验类型"); + if (StringUtils.isBlank(typeValue)) { + throw new RuntimeException("检验类型不能为空"); + } + item.setInspectionTypeNo(QcExcelImportHelper.resolveInspectionTypeNo(typeValue, types)); + item.setMethodNo(QcExcelImportHelper.getString(row, headers, "方法编码")); + if (StringUtils.isBlank(item.getMethodNo())) { + throw new RuntimeException("方法编码不能为空"); + } + assertMethodExists(site, item.getMethodNo(), item.getInspectionTypeNo()); + item.setDefaultValue(blankToNull(QcExcelImportHelper.getString(row, headers, "参照值"))); + item.setMaxValue(QcExcelImportHelper.getDecimal(row, headers, "最大值")); + item.setMinValue(QcExcelImportHelper.getDecimal(row, headers, "最小值")); + if ("T".equals(item.getValueTypeDb())) { + item.setValueType("文本"); + item.setMaxValue(null); + item.setMinValue(null); } else { - task.setMinValue(BigDecimal.valueOf(row.getCell(4).getNumericCellValue())); + item.setValueType("数字"); + if (item.getMaxValue() != null && item.getMinValue() != null + && item.getMaxValue().compareTo(item.getMinValue()) < 0) { + throw new RuntimeException("最大值不能小于最小值"); + } } - } else { - task.setValueTypeDb(null); + item.setSamplingProgrammeNo(resolveSamplingProgrammeNo( + QcExcelImportHelper.getString(row, headers, "抽样方案编码"), programmes)); + item.setSamplingLevelNo(resolveSamplingLevelNo( + QcExcelImportHelper.getString(row, headers, "检验水平编码"), levels)); + if (StringUtils.isNotBlank(item.getSamplingProgrammeNo()) && StringUtils.isBlank(item.getSamplingLevelNo())) { + throw new RuntimeException("填写抽样方案时必须填写检验水平"); + } + item.setItemSamplingQuantity(QcExcelImportHelper.getDecimal(row, headers, "抽样数量")); + item.setDefaultSamplingProportion(QcExcelImportHelper.getDecimal(row, headers, "抽样比例%", "抽样比例")); + String pictureFlag = QcExcelImportHelper.resolveYn(QcExcelImportHelper.getString(row, headers, "需上传图片")); + item.setPictureFlag(StringUtils.defaultIfBlank(pictureFlag, "N")); + toSave.add(item); + } catch (RuntimeException e) { + errors.add("第" + excelRow + "行:" + e.getMessage()); } - if (row.getCell(2) == null) { - task.setDefaultValue(null); - } else { - task.setDefaultValue(row.getCell(2).getRawValue()); + } + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("导入失败:" + e.getMessage()); + } + if (toSave.isEmpty() && errors.isEmpty()) { + throw new RuntimeException("没有可导入的数据,请检查Excel内容"); + } + if (!errors.isEmpty()) { + throw new RuntimeException("导入失败:
" + String.join("
", errors)); + } + for (QcItemData item : toSave) { + item.setItemNo(QcExcelImportHelper.buildTypePrefixNo(item.getInspectionTypeNo(), qcMapper.getItemNo(item))); + qcMapper.qcItemSave(item); + qcMapper.qcItemMethodSave(item); + } + } + + /** + * 检验方法导入 + **/ + @Override + @Transactional + public void uploadMethodExcel(MultipartFile file, GetParamInData data) { + String site = QcExcelImportHelper.resolveSite(data.getSite()); + String buNo = QcExcelImportHelper.resolveBuNo(); + List types = loadInspectionTypes(site); + List errors = new ArrayList<>(); + List toSave = new ArrayList<>(); + try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { + Sheet sheet = QcExcelImportHelper.getDataSheet(workbook, QcExcelImportHelper.FLAG_METHOD); + Map headers = QcExcelImportHelper.readHeaderIndex(sheet.getRow(0)); + int lastRow = sheet.getLastRowNum(); + for (int r = 1; r <= lastRow; r++) { + Row row = sheet.getRow(r); + if (QcExcelImportHelper.isBlankRow(row, headers)) { + continue; + } + int excelRow = r + 1; + try { + QcMethodData method = new QcMethodData(); + method.setSite(site); + method.setBuNo(buNo); + method.setCreateBy(data.getCreateBy()); + method.setUpdateBy(data.getCreateBy()); + method.setMethodName(QcExcelImportHelper.getString(row, headers, "方法名称")); + if (StringUtils.isBlank(method.getMethodName())) { + throw new RuntimeException("方法名称不能为空"); + } + String typeValue = QcExcelImportHelper.getString(row, headers, "检验类型"); + if (StringUtils.isBlank(typeValue)) { + throw new RuntimeException("检验类型不能为空"); + } + method.setInspectionTypeNo(QcExcelImportHelper.resolveInspectionTypeNo(typeValue, types)); + method.setMethodRemark(blankToNull(QcExcelImportHelper.getString(row, headers, "检验方法说明"))); + toSave.add(method); + } catch (RuntimeException e) { + errors.add("第" + excelRow + "行:" + e.getMessage()); } - task.setItemType("D"); - task.setCreatedBy(data.getCreateBy()); - task.setMethodNo(row.getCell(5).getRawValue()); - // 新增 - qcMapper.qcItemSave(task); - // 新增项目里的方法 - qcMapper.qcItemMethodSave(task); } - }catch (Exception e){ - throw new RuntimeException("导入失败:"+e.getMessage()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("导入失败:" + e.getMessage()); + } + if (toSave.isEmpty() && errors.isEmpty()) { + throw new RuntimeException("没有可导入的数据,请检查Excel内容"); + } + if (!errors.isEmpty()) { + throw new RuntimeException("导入失败:
" + String.join("
", errors)); + } + for (QcMethodData method : toSave) { + method.setMethodNo(QcExcelImportHelper.buildTypePrefixNo(method.getInspectionTypeNo(), qcMapper.getMethodNo(method))); + qcMapper.qcMethodSave(method); + } + } + + @Override + public void downloadQcImportTemplate(String flag, String site, HttpServletResponse response) { + String optionSite = StringUtils.isBlank(site) ? "" : QcExcelImportHelper.resolveSite(site); + try (Workbook workbook = QcExcelImportHelper.createTemplateWorkbook(flag, buildExcelTemplateOptions(optionSite)); + OutputStream os = response.getOutputStream()) { + String fileName = URLEncoder.encode(QcExcelImportHelper.templateFileName(flag), "UTF-8").replaceAll("\\+", "%20"); + response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"); + response.setHeader("Content-Disposition", "attachment;filename*=UTF-8''" + fileName); + workbook.write(os); + os.flush(); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("下载导入模板失败:" + e.getMessage()); } } + private QcExcelImportHelper.TemplateOptions buildExcelTemplateOptions(String site) { + QcExcelImportHelper.TemplateOptions options = new QcExcelImportHelper.TemplateOptions(); + if (StringUtils.isBlank(site)) { + return options; + } + List typeNames = new ArrayList<>(); + for (QcInspectionTypeData type : loadInspectionTypes(site)) { + if (type != null && StringUtils.isNotBlank(type.getInspectionTypeName())) { + typeNames.add(type.getInspectionTypeName()); + } + } + options.setInspectionTypeNames(typeNames); + return options; + } + + private List loadInspectionTypes(String site) { + QcInspectionTypeData query = new QcInspectionTypeData(); + query.setSite(site); + List list = qcMapper.inspectionTypeSearch(query); + return list == null ? Collections.emptyList() : list; + } + + private List loadSamplingProgrammes(String site) { + QcSamplingInspectionProgrammeData query = new QcSamplingInspectionProgrammeData(); + query.setSite(site); + IPage page = qcMapper.samplingProgrammeSearch( + new Page(1, 10000), query); + return page == null || page.getRecords() == null ? Collections.emptyList() : page.getRecords(); + } + + private List loadSamplingLevels(String site) { + QcSamplingInspectionLevelData query = new QcSamplingInspectionLevelData(); + query.setSite(site); + IPage page = qcMapper.samplingLevelSearch( + new Page(1, 10000), query); + return page == null || page.getRecords() == null ? Collections.emptyList() : page.getRecords(); + } + + private String resolveSamplingProgrammeNo(String value, List programmes) { + if (StringUtils.isBlank(value)) { + return null; + } + String input = value.trim(); + if (programmes != null) { + for (QcSamplingInspectionProgrammeData programme : programmes) { + if (programme != null && input.equals(programme.getSamplingProgrammeNo())) { + return programme.getSamplingProgrammeNo(); + } + } + for (QcSamplingInspectionProgrammeData programme : programmes) { + if (programme != null && input.equals(programme.getSamplingProgrammeDesc())) { + return programme.getSamplingProgrammeNo(); + } + } + } + throw new RuntimeException("抽样方案不存在:" + value); + } + + private String resolveSamplingLevelNo(String value, List levels) { + if (StringUtils.isBlank(value)) { + return null; + } + String input = value.trim(); + if (levels != null) { + for (QcSamplingInspectionLevelData level : levels) { + if (level != null && input.equals(level.getSamplingLevelNo())) { + return level.getSamplingLevelNo(); + } + } + for (QcSamplingInspectionLevelData level : levels) { + if (level != null && input.equals(level.getSamplingLevelDesc())) { + return level.getSamplingLevelNo(); + } + } + } + throw new RuntimeException("检验水平不存在:" + value); + } + + private void assertMethodExists(String site, String methodNo, String inspectionTypeNo) { + QcMethodData query = new QcMethodData(); + query.setSite(site); + query.setMethodNo(methodNo); + List list = qcMapper.queryMethodList(query); + QcMethodData found = null; + if (list != null) { + for (QcMethodData method : list) { + if (methodNo.equals(method.getMethodNo())) { + found = method; + break; + } + } + } + if (found == null) { + throw new RuntimeException("检验方法不存在:" + methodNo); + } + if (StringUtils.isNotBlank(inspectionTypeNo) && StringUtils.isNotBlank(found.getInspectionTypeNo()) + && !inspectionTypeNo.equals(found.getInspectionTypeNo())) { + throw new RuntimeException("方法编码【" + methodNo + "】与检验类型不匹配"); + } + } + + private String blankToNull(String value) { + return StringUtils.isBlank(value) ? null : value; + } + // ======================= 质检员信息维护 ======================= /** @@ -735,164 +937,83 @@ public class QcServiceImpl implements QcService { @Override @Transactional public void uploadTemplateExcel(MultipartFile file, GetParamInData paraData) { - try{ - // 转流 - InputStream is = file.getInputStream(); - // 读取工作簿 - XSSFWorkbook workbook = new XSSFWorkbook(is); - // 读取工作表 - XSSFSheet sheet = workbook.getSheetAt(0); - // 获取行数 - int rows = sheet.getPhysicalNumberOfRows(); - // 声明对象 - QcTemplateData task = null; - //排除标准数据下的空白行数据 - int nonEmptyRows = 0; - for (int i = 0; i < rows; i++) { - XSSFRow row = sheet.getRow(i); - if (row != null && row.getCell(0) != null && StringUtils.isNotBlank(row.getCell(0).toString())) { - nonEmptyRows++; - } - } - // 遍历每一行(从第二行开始) - for (int j = 1; j < nonEmptyRows; j++) { - // 实例化对象 - task = new QcTemplateData(); - // 获得该行 - XSSFRow row = sheet.getRow(j); - //截取site和bu - String[] split = paraData.getSite().split("_"); - // 为对象赋值 - task.setSite(split[0]); - task.setBuNo(split[1]); - //获取用户传入的 检验类型描述 - String type = row.getCell(9).getStringCellValue(); - //根据type 设置检验类型编码 和 检验模板编码 - if (type.equals("IPQC")){ - task.setInspectionTypeNo("101"); - task.setTemplateId("IPQC-" + qcMapper.getTemplateNo(task)); - }else if (type.equals("IQC")){ - task.setInspectionTypeNo("105"); - task.setTemplateId("IQC-" + qcMapper.getTemplateNo(task)); - }else if (type.equals("FAI")){ - task.setInspectionTypeNo("106"); - task.setTemplateId("FAI-" + qcMapper.getTemplateNo(task)); - }else if (type.equals("FQC")){ - task.setInspectionTypeNo("107"); - task.setTemplateId("FQC-" + qcMapper.getTemplateNo(task)); - }else if (type.equals("PQC")){ - task.setInspectionTypeNo("108"); - task.setTemplateId("PQC-" + qcMapper.getTemplateNo(task)); - } -// task.setInspectionTypeNo(row.getCell(9).getRawValue()); - - // 获取编码 -// String templateNo = qcMapper.getTemplateNo(task); -// if ("101".equals(task.getInspectionTypeNo()) || "102".equals(task.getInspectionTypeNo()) || "103".equals(task.getInspectionTypeNo()) || "104".equals(task.getInspectionTypeNo())) { -// task.setTemplateId("IPQC-" + templateNo); -// } else if ("105".equals(task.getInspectionTypeNo())) { -// task.setTemplateId("IQC-" + templateNo); -// } else if ("106".equals(task.getInspectionTypeNo())) { -// task.setTemplateId("FAI-" + templateNo); -// } else if ("107".equals(task.getInspectionTypeNo())) { -// task.setTemplateId("FQC-" + templateNo); -// } else if ("108".equals(task.getInspectionTypeNo())) { -// task.setTemplateId("PQC-" + templateNo); -// } - //判断用户是否输入 抽样方案编码、默认抽样数量和默认抽样比例 其中之一 - boolean isFlag = false; - task.setTemplateName(row.getCell(0).getStringCellValue()); -// task.setSamplingProgrammeNo(row.getCell(1).getRawValue()); -// task.setSamplingLevelNo(row.getCell(2).getRawValue()); - if (row.getCell(1)==null){ - task.setSamplingProgrammeNo(null); - }else { - isFlag = true; - task.setSamplingProgrammeNo(row.getCell(1).getRawValue()); - } - - if (row.getCell(2)==null){ - task.setSamplingLevelNo(null); - }else { - task.setSamplingLevelNo(row.getCell(2).getRawValue()); - } - - if (row.getCell(3) == null){ - task.setInspectionCycle(null); - }else { - task.setInspectionCycle(BigDecimal.valueOf(row.getCell(3).getNumericCellValue())); - } - if (row.getCell(4) == null){ - task.setDefaultSamplingQuantity(null); - }else { - isFlag = true; - task.setDefaultSamplingQuantity(BigDecimal.valueOf(row.getCell(4).getNumericCellValue())); - } - if (row.getCell(5) == null){ - task.setDefaultSamplingProportion(null); - }else { - isFlag = true; - task.setDefaultSamplingProportion(BigDecimal.valueOf(row.getCell(5).getNumericCellValue())); - } - if (!isFlag){ - throw new RuntimeException("抽象方案编码、默认抽样数量和默认抽样比例至少填写一个!"); - } - if (row.getCell(6) == null){ - task.setAql(null); - }else { - task.setAql(BigDecimal.valueOf(row.getCell(6).getNumericCellValue())); - } - - if (row.getCell(7) == null){ - task.setAc(null); - }else { - task.setAc(BigDecimal.valueOf(row.getCell(7).getNumericCellValue())); - } - - if (row.getCell(8) == null){ - task.setRe(null); - }else { - task.setRe(BigDecimal.valueOf(row.getCell(8).getNumericCellValue())); - } - if (row.getCell(10) == null){ - task.setTemplateRemark(null); - }else { - task.setTemplateRemark(row.getCell(10).getStringCellValue()); - } -// task.setTemplateVersion(row.getCell(11).getRawValue()); - if (row.getCell(11)==null){ - task.setTemplateVersion(null); - }else { - task.setTemplateVersion(row.getCell(11).getRawValue()); - } - task.setCreateBy(paraData.getCreateBy()); - // 查重 - QcTemplateData templateData = qcMapper.selectTemplateByDesc(task); - if(templateData != null){ - throw new RuntimeException("该模板已存在已存在!"); + String site = QcExcelImportHelper.resolveSite(paraData.getSite()); + String buNo = QcExcelImportHelper.resolveBuNo(); + List types = loadInspectionTypes(site); + List programmes = loadSamplingProgrammes(site); + List levels = loadSamplingLevels(site); + List errors = new ArrayList<>(); + List toSave = new ArrayList<>(); + try (Workbook workbook = QcExcelImportHelper.openWorkbook(file)) { + Sheet sheet = QcExcelImportHelper.getDataSheet(workbook, QcExcelImportHelper.FLAG_TEMPLATE); + Map headers = QcExcelImportHelper.readHeaderIndex(sheet.getRow(0)); + int lastRow = sheet.getLastRowNum(); + for (int r = 1; r <= lastRow; r++) { + Row row = sheet.getRow(r); + if (QcExcelImportHelper.isBlankRow(row, headers)) { + continue; } - // 新增模板 - qcMapper.templateSave(task); - // 新增模板中的项目 - int cells = row.getLastCellNum(); - for (int i = 1; i <= cells - 12; i++) { - String itemNo = row.getCell(11 + i).getStringCellValue(); - String itemType = "D"; - QcTemplateData data = qcMapper.selectItem(itemNo,itemType,split[1]); - if (data== null){ - throw new RuntimeException("BU为"+split[1]+"时,检验项目不存在:"+itemNo); + int excelRow = r + 1; + try { + QcTemplateData task = new QcTemplateData(); + task.setSite(site); + task.setBuNo(buNo); + task.setCreateBy(paraData.getCreateBy()); + task.setUpdateBy(paraData.getCreateBy()); + task.setTemplateName(QcExcelImportHelper.getString(row, headers, "检验模板名称")); + if (StringUtils.isBlank(task.getTemplateName())) { + throw new RuntimeException("检验模板名称不能为空"); } - data.setTemplateId(task.getTemplateId()); - data.setOrderId(i); - // 新增 - qcMapper.addItemDetails(data); + String typeValue = QcExcelImportHelper.getString(row, headers, "检验类型"); + if (StringUtils.isBlank(typeValue)) { + throw new RuntimeException("检验类型不能为空"); + } + task.setInspectionTypeNo(QcExcelImportHelper.resolveInspectionTypeNo(typeValue, types)); + task.setSamplingProgrammeNo(resolveSamplingProgrammeNo( + QcExcelImportHelper.getString(row, headers, "抽样方案编码"), programmes)); + task.setSamplingLevelNo(resolveSamplingLevelNo( + QcExcelImportHelper.getString(row, headers, "检验水平编码"), levels)); + task.setInspectionCycle(QcExcelImportHelper.getDecimal(row, headers, "检验完成时间(h)")); + task.setDefaultSamplingQuantity(QcExcelImportHelper.getDecimal(row, headers, "默认抽样数量")); + task.setDefaultSamplingProportion(QcExcelImportHelper.getDecimal(row, headers, "默认抽样比例")); + if (StringUtils.isBlank(task.getSamplingProgrammeNo()) + && task.getDefaultSamplingQuantity() == null + && task.getDefaultSamplingProportion() == null) { + throw new RuntimeException("抽样方案编码、默认抽样数量和默认抽样比例至少填写一个"); + } + if (StringUtils.isNotBlank(task.getSamplingProgrammeNo()) && StringUtils.isBlank(task.getSamplingLevelNo())) { + throw new RuntimeException("填写抽样方案时必须填写检验水平"); + } + task.setAql(QcExcelImportHelper.getDecimal(row, headers, "AQL")); + task.setAc(QcExcelImportHelper.getDecimal(row, headers, "AC")); + task.setRe(QcExcelImportHelper.getDecimal(row, headers, "RE")); + task.setTemplateVersion(blankToNull(QcExcelImportHelper.getString(row, headers, "版本号"))); + task.setTemplateRemark(blankToNull(QcExcelImportHelper.getString(row, headers, "检验模板备注"))); + if (StringUtils.isNotBlank(task.getTemplateVersion())) { + QcTemplateData exists = qcMapper.selectTemplateByDesc(task); + if (exists != null) { + throw new RuntimeException("该模板已存在"); + } + } + toSave.add(task); + } catch (RuntimeException e) { + errors.add("第" + excelRow + "行:" + e.getMessage()); } - } - //给新导入为null的orderId排序 - qcMapper.updateQCDetailOrderIdNull(); - }catch (Exception e){ - throw new RuntimeException("导入失败:"+e.getMessage()); + } catch (RuntimeException e) { + throw e; + } catch (Exception e) { + throw new RuntimeException("导入失败:" + e.getMessage()); + } + if (toSave.isEmpty() && errors.isEmpty()) { + throw new RuntimeException("没有可导入的数据,请检查Excel内容"); + } + if (!errors.isEmpty()) { + throw new RuntimeException("导入失败:
" + String.join("
", errors)); + } + for (QcTemplateData task : toSave) { + task.setTemplateId(QcExcelImportHelper.buildTypePrefixNo(task.getInspectionTypeNo(), qcMapper.getTemplateNo(task))); + qcMapper.templateSave(task); } } diff --git a/src/main/java/com/heai/modules/pms/service/QcService.java b/src/main/java/com/heai/modules/pms/service/QcService.java index 446005b..1a5f8d6 100644 --- a/src/main/java/com/heai/modules/pms/service/QcService.java +++ b/src/main/java/com/heai/modules/pms/service/QcService.java @@ -80,6 +80,10 @@ public interface QcService { void taskUploadExcel(MultipartFile file, GetParamInData data); + void uploadMethodExcel(MultipartFile file, GetParamInData data); + + void downloadQcImportTemplate(String flag, String site, javax.servlet.http.HttpServletResponse response); + void saveItemDetailed(QcTemplateData data); PageUtils samplingQtySearch(QcSamplingInspectionQtyData data); diff --git a/src/main/java/com/heai/modules/pms/util/QcExcelImportHelper.java b/src/main/java/com/heai/modules/pms/util/QcExcelImportHelper.java new file mode 100644 index 0000000..1503052 --- /dev/null +++ b/src/main/java/com/heai/modules/pms/util/QcExcelImportHelper.java @@ -0,0 +1,500 @@ +package com.heai.modules.pms.util; + +import com.heai.modules.pms.data.QcInspectionTypeData; +import org.apache.commons.lang3.StringUtils; +import org.apache.poi.ss.usermodel.BorderStyle; +import org.apache.poi.ss.usermodel.Cell; +import org.apache.poi.ss.usermodel.CellStyle; +import org.apache.poi.ss.usermodel.DataFormatter; +import org.apache.poi.ss.usermodel.DataValidation; +import org.apache.poi.ss.usermodel.DataValidationConstraint; +import org.apache.poi.ss.usermodel.DataValidationHelper; +import org.apache.poi.ss.usermodel.FillPatternType; +import org.apache.poi.ss.usermodel.Font; +import org.apache.poi.ss.usermodel.HorizontalAlignment; +import org.apache.poi.ss.usermodel.IndexedColors; +import org.apache.poi.ss.usermodel.Name; +import org.apache.poi.ss.usermodel.Row; +import org.apache.poi.ss.usermodel.Sheet; +import org.apache.poi.ss.usermodel.VerticalAlignment; +import org.apache.poi.ss.usermodel.Workbook; +import org.apache.poi.ss.usermodel.WorkbookFactory; +import org.apache.poi.ss.util.CellRangeAddressList; +import org.apache.poi.xssf.usermodel.XSSFDataValidation; +import org.apache.poi.xssf.usermodel.XSSFWorkbook; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.io.InputStream; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +/** + * QC 检验方法 / 项目 / 模板 Excel 导入辅助 + */ +public final class QcExcelImportHelper { + + public static final String FLAG_METHOD = "method"; + public static final String FLAG_ITEM = "item"; + public static final String FLAG_TEMPLATE = "template"; + public static final String DEFAULT_BU_NO = "*"; + + private static final int DATA_ROW_END = 1000; + private static final String OPTIONS_SHEET = "下拉选项"; + private static final DataFormatter FORMATTER = new DataFormatter(); + + private static final Map TYPE_ALIAS; + static { + Map map = new HashMap<>(); + map.put("IPQC", "101"); + map.put("IQC", "105"); + map.put("FAI", "106"); + map.put("FQC", "107"); + map.put("PQC", "108"); + map.put("SQC", "109"); + map.put("OQC", "110"); + TYPE_ALIAS = Collections.unmodifiableMap(map); + } + + public static class TemplateOptions { + private List inspectionTypeNames = new ArrayList<>(); + + public List getInspectionTypeNames() { + return inspectionTypeNames; + } + + public void setInspectionTypeNames(List inspectionTypeNames) { + this.inspectionTypeNames = inspectionTypeNames == null ? new ArrayList() : inspectionTypeNames; + } + } + + private QcExcelImportHelper() { + } + + public static String[] methodHeaders() { + return new String[]{"*方法名称", "*检验类型", "检验方法说明"}; + } + + public static String[] itemHeaders() { + return new String[]{ + "*项目名称", "*检验值类型", "*检验类型", "*方法编码", + "抽样方案编码", "检验水平编码", "参照值", "最大值", "最小值", + "抽样数量", "抽样比例%", "需上传图片" + }; + } + + public static String[] templateHeaders() { + return new String[]{ + "*检验模板名称", "*检验类型", "抽样方案编码", "检验水平编码", + "检验完成时间(h)", "默认抽样数量", "默认抽样比例", "AQL", "AC", "RE", + "版本号", "检验模板备注" + }; + } + + public static String dataSheetName(String flag) { + if (FLAG_METHOD.equals(flag)) { + return "检验方法"; + } + if (FLAG_ITEM.equals(flag)) { + return "检验项目"; + } + if (FLAG_TEMPLATE.equals(flag)) { + return "检验模板"; + } + throw new RuntimeException("不支持的导入模板类型"); + } + + public static Sheet getDataSheet(Workbook workbook, String flag) { + if (workbook == null || workbook.getNumberOfSheets() == 0) { + throw new RuntimeException("Excel 中没有工作表"); + } + String expected = dataSheetName(flag); + Sheet named = workbook.getSheet(expected); + if (named != null) { + return named; + } + for (int i = 0; i < workbook.getNumberOfSheets(); i++) { + Sheet sheet = workbook.getSheetAt(i); + String name = sheet.getSheetName(); + if (OPTIONS_SHEET.equals(name) || "填写说明".equals(name)) { + continue; + } + if (workbook.isSheetHidden(i) || workbook.isSheetVeryHidden(i)) { + continue; + } + return sheet; + } + throw new RuntimeException("未找到数据工作表,请使用系统下载的导入模板"); + } + + public static Workbook createTemplateWorkbook(String flag, TemplateOptions options) { + XSSFWorkbook workbook = new XSSFWorkbook(); + if (options == null) { + options = new TemplateOptions(); + } + String sheetName = dataSheetName(flag); + String[] headers; + String note; + if (FLAG_METHOD.equals(flag)) { + headers = methodHeaders(); + note = "带*列为必填。检验类型请用下拉选择。方法编码由系统自动生成。工厂取当前用户默认工厂,不必填写。"; + } else if (FLAG_ITEM.equals(flag)) { + headers = itemHeaders(); + note = "带*列为必填。检验类型、检验值类型、需上传图片请用下拉选择。方法编码、抽样方案编码、检验水平编码请按系统中已有编码手填。填写抽样方案时须同时填写检验水平。项目编码由系统自动生成。"; + } else { + headers = templateHeaders(); + note = "带*列为必填。检验类型请用下拉选择。抽样方案编码、检验水平编码请按系统中已有编码手填。抽样方案编码、默认抽样数量、默认抽样比例至少填一项;填写抽样方案时须同时填写检验水平。模板编码由系统自动生成。"; + } + + Sheet sheet = workbook.createSheet(sheetName); + CellStyle requiredStyle = createHeaderStyle(workbook, true); + CellStyle optionalStyle = createHeaderStyle(workbook, false); + Row headerRow = sheet.createRow(0); + for (int i = 0; i < headers.length; i++) { + Cell cell = headerRow.createCell(i); + cell.setCellValue(headers[i]); + cell.setCellStyle(headers[i].startsWith("*") ? requiredStyle : optionalStyle); + sheet.setColumnWidth(i, 18 * 256); + } + + CellStyle textStyle = workbook.createCellStyle(); + textStyle.setDataFormat(workbook.createDataFormat().getFormat("@")); + applyTextColumns(sheet, headers, textStyle, + "检验类型", "检验值类型", "方法编码", "抽样方案编码", "检验水平编码", "需上传图片"); + + writeOptionsSheet(workbook, options); + addDropdown(sheet, headers, "检验类型", "optInspectionType"); + addDropdown(sheet, headers, "检验值类型", "optValueType"); + addDropdown(sheet, headers, "需上传图片", "optYn"); + + Sheet noteSheet = workbook.createSheet("填写说明"); + noteSheet.setColumnWidth(0, 120 * 256); + noteSheet.createRow(0).createCell(0).setCellValue(note); + noteSheet.createRow(1).createCell(0).setCellValue("请从第2行开始填写数据。红色带*列为必填。检验类型、检验值类型、需上传图片请点单元格右侧箭头选择;方法编码、抽样方案编码、检验水平编码请手填系统中已有编码。"); + noteSheet.createRow(2).createCell(0).setCellValue("工厂取当前登录人默认工厂,部门固定为 *,无需在Excel中填写。"); + workbook.setSheetHidden(workbook.getSheetIndex(OPTIONS_SHEET), true); + workbook.setSheetOrder(sheetName, 0); + workbook.setActiveSheet(workbook.getSheetIndex(sheetName)); + return workbook; + } + + public static String templateFileName(String flag) { + if (FLAG_METHOD.equals(flag)) { + return "检验方法导入模板.xlsx"; + } + if (FLAG_ITEM.equals(flag)) { + return "检验项目导入模板.xlsx"; + } + if (FLAG_TEMPLATE.equals(flag)) { + return "检验模板导入模板.xlsx"; + } + return "QC导入模板.xlsx"; + } + + public static Workbook openWorkbook(MultipartFile file) throws IOException { + if (file == null || file.isEmpty()) { + throw new RuntimeException("请先上传文件"); + } + try (InputStream is = file.getInputStream()) { + return WorkbookFactory.create(is); + } + } + + public static Map readHeaderIndex(Row headerRow) { + if (headerRow == null) { + throw new RuntimeException("Excel 缺少表头行"); + } + Map index = new LinkedHashMap<>(); + short last = headerRow.getLastCellNum(); + for (int i = 0; i < last; i++) { + String name = normalizeHeader(cellString(headerRow.getCell(i))); + if (StringUtils.isNotBlank(name) && !index.containsKey(name)) { + index.put(name, i); + } + } + if (index.isEmpty()) { + throw new RuntimeException("Excel 表头为空,请使用系统下载的导入模板"); + } + return index; + } + + public static String normalizeHeader(String header) { + if (header == null) { + return ""; + } + String value = header.trim(); + if (value.startsWith("*")) { + value = value.substring(1).trim(); + } + if (value.endsWith("%")) { + value = value.substring(0, value.length() - 1).trim(); + } + return value; + } + + public static String getString(Row row, Map headers, String... names) { + Integer idx = findIndex(headers, names); + if (idx == null) { + return ""; + } + return cellString(row.getCell(idx)); + } + + public static BigDecimal getDecimal(Row row, Map headers, String... names) { + String value = getString(row, headers, names); + if (StringUtils.isBlank(value)) { + return null; + } + try { + return new BigDecimal(value.replace(",", "").trim()); + } catch (NumberFormatException e) { + throw new RuntimeException("【" + names[0] + "】不是有效数字:" + value); + } + } + + public static boolean isBlankRow(Row row, Map headers) { + if (row == null) { + return true; + } + for (Integer idx : headers.values()) { + if (StringUtils.isNotBlank(cellString(row.getCell(idx)))) { + return false; + } + } + return true; + } + + public static String resolveInspectionTypeNo(String value, List types) { + if (StringUtils.isBlank(value)) { + return ""; + } + String input = value.trim(); + if (types != null) { + for (QcInspectionTypeData type : types) { + if (input.equals(type.getInspectionTypeNo()) || input.equalsIgnoreCase(type.getInspectionTypeName())) { + return type.getInspectionTypeNo(); + } + } + } + String alias = TYPE_ALIAS.get(input.toUpperCase()); + if (alias != null) { + return alias; + } + throw new RuntimeException("检验类型无法识别:" + value); + } + + public static String resolveYn(String value) { + if (StringUtils.isBlank(value)) { + return null; + } + String input = value.trim(); + if ("Y".equalsIgnoreCase(input) || "是".equals(input) || "true".equalsIgnoreCase(input) || "1".equals(input)) { + return "Y"; + } + if ("N".equalsIgnoreCase(input) || "否".equals(input) || "false".equalsIgnoreCase(input) || "0".equals(input)) { + return "N"; + } + throw new RuntimeException("只能选择 是 或 否"); + } + + public static String resolveValueTypeDb(String value) { + if (StringUtils.isBlank(value)) { + return ""; + } + String input = value.trim(); + if ("T".equalsIgnoreCase(input) || "文本".equals(input) || "文字".equals(input)) { + return "T"; + } + if ("N".equalsIgnoreCase(input) || "数字".equals(input) || "数值".equals(input)) { + return "N"; + } + throw new RuntimeException("检验值类型请选择 文本 或 数字"); + } + + public static String resolveSite(String siteParam) { + if (StringUtils.isBlank(siteParam)) { + throw new RuntimeException("未获取到当前用户工厂"); + } + String site = siteParam.trim(); + int idx = site.indexOf('_'); + if (idx > 0) { + return site.substring(0, idx); + } + return site; + } + + public static String resolveBuNo() { + return DEFAULT_BU_NO; + } + + public static String buildTypePrefixNo(String inspectionTypeNo, String seqNo) { + if ("101".equals(inspectionTypeNo) || "102".equals(inspectionTypeNo) + || "103".equals(inspectionTypeNo) || "104".equals(inspectionTypeNo)) { + return "IPQC-" + seqNo; + } + if ("105".equals(inspectionTypeNo)) { + return "IQC-" + seqNo; + } + if ("106".equals(inspectionTypeNo)) { + return "FAI-" + seqNo; + } + if ("107".equals(inspectionTypeNo)) { + return "FQC-" + seqNo; + } + if ("108".equals(inspectionTypeNo)) { + return "PQC-" + seqNo; + } + if ("109".equals(inspectionTypeNo)) { + return "SQC-" + seqNo; + } + if ("110".equals(inspectionTypeNo)) { + return "OQC-" + seqNo; + } + throw new RuntimeException("不支持的检验类型编码:" + inspectionTypeNo); + } + + public static String cellString(Cell cell) { + if (cell == null) { + return ""; + } + return StringUtils.trimToEmpty(FORMATTER.formatCellValue(cell)); + } + + private static Integer findIndex(Map headers, String... names) { + if (headers == null || names == null) { + return null; + } + for (String name : names) { + Integer idx = headers.get(normalizeHeader(name)); + if (idx != null) { + return idx; + } + } + return null; + } + + private static CellStyle createHeaderStyle(Workbook workbook, boolean required) { + CellStyle style = workbook.createCellStyle(); + Font font = workbook.createFont(); + font.setBold(true); + font.setColor(required ? IndexedColors.RED.getIndex() : IndexedColors.WHITE.getIndex()); + style.setFont(font); + style.setFillForegroundColor(required ? IndexedColors.LIGHT_YELLOW.getIndex() : IndexedColors.DARK_BLUE.getIndex()); + style.setFillPattern(FillPatternType.SOLID_FOREGROUND); + style.setAlignment(HorizontalAlignment.CENTER); + style.setVerticalAlignment(VerticalAlignment.CENTER); + style.setBorderBottom(BorderStyle.THIN); + style.setBorderLeft(BorderStyle.THIN); + style.setBorderRight(BorderStyle.THIN); + style.setBorderTop(BorderStyle.THIN); + style.setWrapText(true); + return style; + } + + private static void writeOptionsSheet(Workbook workbook, TemplateOptions options) { + Sheet sheet = workbook.createSheet(OPTIONS_SHEET); + String[] titles = {"检验类型", "检验值类型", "需上传图片"}; + Row titleRow = sheet.createRow(0); + for (int i = 0; i < titles.length; i++) { + titleRow.createCell(i).setCellValue(titles[i]); + sheet.setColumnWidth(i, 18 * 256); + } + writeOptionColumn(sheet, 0, options.getInspectionTypeNames()); + writeOptionColumn(sheet, 1, Arrays.asList("文本", "数字")); + writeOptionColumn(sheet, 2, Arrays.asList("是", "否")); + + createName(workbook, "optInspectionType", 0, sizeOf(options.getInspectionTypeNames())); + createName(workbook, "optValueType", 1, 2); + createName(workbook, "optYn", 2, 2); + } + + private static void writeOptionColumn(Sheet sheet, int col, List values) { + if (values == null) { + return; + } + int rowIdx = 1; + for (String value : values) { + if (StringUtils.isBlank(value)) { + continue; + } + Row row = sheet.getRow(rowIdx); + if (row == null) { + row = sheet.createRow(rowIdx); + } + row.createCell(col).setCellValue(value.trim()); + rowIdx++; + } + } + + private static int sizeOf(List values) { + if (values == null) { + return 0; + } + int count = 0; + for (String value : values) { + if (StringUtils.isNotBlank(value)) { + count++; + } + } + return count; + } + + private static void createName(Workbook workbook, String name, int col, int size) { + if (size <= 0) { + return; + } + String colLetter = colLetter(col); + Name named = workbook.createName(); + named.setNameName(name); + named.setRefersToFormula("'" + OPTIONS_SHEET + "'!$" + colLetter + "$2:$" + colLetter + "$" + (size + 1)); + } + + private static void applyTextColumns(Sheet sheet, String[] headers, CellStyle textStyle, String... names) { + for (String name : names) { + for (int i = 0; i < headers.length; i++) { + if (normalizeHeader(name).equals(normalizeHeader(headers[i]))) { + sheet.setDefaultColumnStyle(i, textStyle); + break; + } + } + } + } + + private static String colLetter(int col) { + return String.valueOf((char) ('A' + col)); + } + + private static void addDropdown(Sheet sheet, String[] headers, String headerName, String namedRange) { + int col = -1; + for (int i = 0; i < headers.length; i++) { + if (normalizeHeader(headerName).equals(normalizeHeader(headers[i]))) { + col = i; + break; + } + } + if (col < 0 || sheet.getWorkbook().getName(namedRange) == null) { + return; + } + DataValidationHelper helper = sheet.getDataValidationHelper(); + DataValidationConstraint constraint = helper.createFormulaListConstraint(namedRange); + CellRangeAddressList addressList = new CellRangeAddressList(1, DATA_ROW_END, col, col); + DataValidation validation = helper.createValidation(constraint, addressList); + validation.setShowErrorBox(true); + validation.setErrorStyle(DataValidation.ErrorStyle.STOP); + validation.createErrorBox("输入错误", "请从下拉列表中选择"); + validation.createPromptBox("请选择", "请从下拉列表中选择"); + validation.setShowPromptBox(true); + if (validation instanceof XSSFDataValidation) { + validation.setSuppressDropDownArrow(true); + } else { + validation.setSuppressDropDownArrow(false); + } + sheet.addValidationData(validation); + } +}