fix(wms):入库单导入表头字段错位、备注丢失

- 取值向右查找时误把下一个标签/选项说明当值(承运方←Tel:、生产商←客户名称:、监管方式←送货地址:、送货地址←查验选项),现遇疑似标签即停止查找
- 备注:xxx同格书写为全角冒号,原仅按半角:拆分导致备注丢失,现全角/半角兼容
- 表头列遍历上界改用getLastCellNum,防空单元格漏扫尾部列"
This commit is contained in:
rcx
2026-08-20 10:06:50 +08:00
parent 77420e9617
commit cc943d164b
@@ -1000,7 +1000,7 @@ public class StockInOrderApplicationService {
for (int rowIdx = 0; rowIdx < 12; rowIdx++) {
Row row = sheet.getRow(rowIdx);
if (row != null) {
for (int colIdx = 0; colIdx < row.getPhysicalNumberOfCells(); colIdx++) {
for (int colIdx = 0; colIdx < row.getLastCellNum(); colIdx++) {
Cell labelCell = row.getCell(colIdx);
if (labelCell != null) {
String labelValue = getCellValueAsString(labelCell);
@@ -1009,42 +1009,45 @@ public class StockInOrderApplicationService {
log.info("表头第{}行第{}列: {}", rowIdx + 1, colIdx + 1, labelValue);
String value = null;
// 先尝试从同一单元格提取值(如果包含冒号
// 先尝试从同一单元格提取值(兼容全角""与半角":",如"备注:xxx"
String extractedFromSameCell = null;
if (labelValue.contains(":")) {
String[] parts = labelValue.split(":", 2);
if (parts.length > 1) {
extractedFromSameCell = parts[1].trim();
String labelKey = labelValue;
int colonIdx = labelValue.indexOf("");
if (colonIdx < 0) {
colonIdx = labelValue.indexOf(":");
}
if (colonIdx >= 0) {
labelKey = labelValue.substring(0, colonIdx).trim();
String afterColon = labelValue.substring(colonIdx + 1).trim();
// 选项说明类标签(如"车型及车牌:3T、5T、8T、10T"、"海关是否查验货物:是、否")冒号后是选项说明而非实际值
if (StringUtils.isNotBlank(afterColon) && !isOptionLabel(labelKey)) {
extractedFromSameCell = afterColon;
}
}
// 跳过空单元格,查找下一个非空单元格作为值(最多向后查找10列)
// 向右查找下一个非空单元格作为值:遇到疑似标签/选项说明(含冒号或命中标签关键词)直接停止,
// 避免把下一个标签(如"承运方:"右侧的"Tel:")或选项说明当成值导致数据错位
String nextCellValue = null;
for (int searchColIdx = colIdx + 1; searchColIdx < colIdx + 10 && searchColIdx < row.getPhysicalNumberOfCells(); searchColIdx++) {
for (int searchColIdx = colIdx + 1; searchColIdx < colIdx + 10 && searchColIdx < row.getLastCellNum(); searchColIdx++) {
Cell valueCell = row.getCell(searchColIdx);
if (valueCell != null) {
String cellValue = getCellValueAsString(valueCell);
if (StringUtils.isNotBlank(cellValue)) {
if (looksLikeLabel(cellValue)) {
log.info("表头第{}行,标签'{}'在第{}列,向右第{}列是另一个标签/选项说明'{}',停止查找", rowIdx + 1, labelValue, colIdx + 1, searchColIdx + 1, cellValue);
break;
}
nextCellValue = cellValue;
log.info("表头第{}行,标签'{}'在第{}列,找到值'{}'在第{}列", rowIdx + 1, labelValue, colIdx + 1, nextCellValue, searchColIdx + 1);
break;
}
}
}
// 优先使用下一个非空单元格的值(如果存在且不为空)
// 如果下一个单元格为空,且同一单元格有冒号后的值,则使用冒号后的值
// 但如果冒号后的值是选项列表(如"3T、5T、8T、10T"),则应该使用下一个单元格的值
// 优先使用右侧紧邻的实际值,其次使用同一单元格冒号后的值
if (StringUtils.isNotBlank(nextCellValue)) {
// 如果冒号后的值是选项列表(包含顿号、逗号等),优先使用下一个单元格的值
if (extractedFromSameCell != null && (extractedFromSameCell.contains("") || extractedFromSameCell.contains(","))) {
value = nextCellValue;
} else {
// 否则优先使用下一个单元格的值
value = nextCellValue;
}
value = nextCellValue;
} else if (StringUtils.isNotBlank(extractedFromSameCell)) {
// 如果下一个单元格为空,使用同一单元格冒号后的值
value = extractedFromSameCell;
}
@@ -2285,6 +2288,41 @@ public class StockInOrderApplicationService {
}
}
/**
* 判断表头标签(冒号前的部分)是否为选项说明类标签:
* 这类标签冒号后跟的是选项说明(如"车型及车牌:3T、5T、8T、10T"、"海关是否查验货物:是、否")而非实际填写值
*/
private boolean isOptionLabel(String labelKey) {
if (StringUtils.isBlank(labelKey)) {
return false;
}
return labelKey.contains("车型及车牌") || labelKey.contains("柜号") || labelKey.contains("海关是否查验货物")
|| labelKey.contains("是否需要报关") || labelKey.contains("是否需要运输");
}
/**
* 判断表头单元格文本是否疑似"标签/选项说明"而非实际值:
* 包含全角""或半角":"(如"Tel"、"客户名称:"),或命中常见标签关键词(如无冒号的"起运/运抵国(地区)")。
* 向右查找标签的值时遇到此类单元格应停止查找,避免把标签文本当成值导致导入数据错位。
*/
private boolean looksLikeLabel(String cellValue) {
if (StringUtils.isBlank(cellValue)) {
return false;
}
if (cellValue.contains("") || cellValue.contains(":")) {
return true;
}
String[] labelKeywords = {"委托编号", "客户名称", "联系人", "车型及车牌", "司机签名", "申报地关区", "生产商",
"报关员", "送货地址", "进出境关别", "起运/运抵国", "运抵国", "承运方", "柜号", "运输方式", "监管方式",
"海关是否查验", "入仓日期", "完成时间", "下单日期", "备注", "Tel", "Fax"};
for (String keyword : labelKeywords) {
if (cellValue.contains(keyword)) {
return true;
}
}
return false;
}
/**
* @description 设置仓库信息
* @author ZhouGY