4 changed files with 858 additions and 220 deletions
-
13src/main/java/com/heai/modules/pms/controller/QcController.java
-
561src/main/java/com/heai/modules/pms/service/Impl/QcServiceImpl.java
-
4src/main/java/com/heai/modules/pms/service/QcService.java
-
500src/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<String, String> TYPE_ALIAS; |
|||
static { |
|||
Map<String, String> 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<String> inspectionTypeNames = new ArrayList<>(); |
|||
|
|||
public List<String> getInspectionTypeNames() { |
|||
return inspectionTypeNames; |
|||
} |
|||
|
|||
public void setInspectionTypeNames(List<String> inspectionTypeNames) { |
|||
this.inspectionTypeNames = inspectionTypeNames == null ? new ArrayList<String>() : 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<String, Integer> readHeaderIndex(Row headerRow) { |
|||
if (headerRow == null) { |
|||
throw new RuntimeException("Excel 缺少表头行"); |
|||
} |
|||
Map<String, Integer> 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<String, Integer> headers, String... names) { |
|||
Integer idx = findIndex(headers, names); |
|||
if (idx == null) { |
|||
return ""; |
|||
} |
|||
return cellString(row.getCell(idx)); |
|||
} |
|||
|
|||
public static BigDecimal getDecimal(Row row, Map<String, Integer> 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<String, Integer> 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<QcInspectionTypeData> 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<String, Integer> 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<String> 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<String> 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); |
|||
} |
|||
} |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue