Merge branch 'dev_v3.0.4' into dev_user0622

This commit is contained in:
王奎兴
2026-07-02 08:35:43 +08:00
20 changed files with 520 additions and 452 deletions
@@ -34,6 +34,9 @@ public class ReservationDeliveryDetailsLink extends BaseVOEntity {
@ApiModelProperty(value = "客户编号")
private String customerNumber;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty(value = "配送名称")
private String deliveryName;
@@ -42,7 +42,8 @@ public class ReservationDeliveryDetailsLinkPO extends BaseVOEntity {
@ApiModelProperty(value = "客户编号")
private String customerNumber;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty(value = "配送名称")
private String deliveryName;
@@ -41,7 +41,8 @@ public class ReservationDeliveryDetailsLinkDO extends BaseVOEntity {
@ApiModelProperty(value = "客户编号")
private String customerNumber;
@ApiModelProperty(value = "客户id")
private Long customerId;
@ApiModelProperty(value = "配送名称")
private String deliveryName;
@@ -63,6 +63,10 @@ public class ReservationInMaterialDetail extends BaseVOEntity {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -62,6 +62,10 @@ public class ReservationInMaterialDetailPO extends ReservationStockInOrderBasePO
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -60,6 +60,10 @@ public class ReservationInMaterialDetailDO extends ReservationStockInOrderBaseDO
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -63,6 +63,10 @@ public class ReservationInMaterialDetailDTO extends BaseVOEntity {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -17,14 +17,13 @@ import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* 维他奶出库Excel解析监听器最终完整
* 维他奶出库Excel解析监听器新模板
* 新模板特点货品明细与客户配送明细左右分栏展示
* 核心功能
* 1. 正确解析多单元格数据解决发票张数/纪录合共读取错误
* 2. 支持动态行数的货品明细/客户配送明细解析
* 1. 左侧栏第1-3行解析表头送货日期/送货线号/车次第5行解析纪录合共第7行起解析货品明细
* 2. 右侧栏第2行解析发票张数/小计第4行起解析客户配送明细
* 3. 自动处理文本型数字转换StringInteger/BigDecimal
* 4. 完整的异常处理和日志记录
*/
@@ -32,83 +31,101 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
// 日志实例
private static final Logger LOGGER = LoggerFactory.getLogger(DynamicDeliveryExcelListener.class);
// ---------------------- 常量定义便于维护 ----------------------
// 正则表达式匹配整数支持正负数
private static final Pattern INTEGER_PATTERN = Pattern.compile("-?\\d+");
// 正则表达式匹配小数支持正负数和整数形式
private static final Pattern DECIMAL_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
// 关键字定义Excel标签匹配
private static final String KEYWORD_DELIVERY_DATE = "送貨日期";
private static final String KEYWORD_PRODUCT_DETAIL = "货品明细";
private static final String KEYWORD_TOTAL_RECORD = "紀錄合共";
private static final String KEYWORD_CUSTOMER_DETAIL = "客户配送明细";
private static final String KEYWORD_INVOICE_COUNT = "發票張數";
private static final String KEYWORD_TOTAL_COUNT = "總計";
private static final String KEYWORD_SUBTOTAL = "小計";
// 支持的日期格式String转Date
private static final String[] SUPPORTED_DATE_FORMATS = {"yyyy-MM-dd", "yyyy/MM/dd", "yyyyMMdd", "yyyy-MM-dd HH:mm:ss"};
// ---------------------- 数据存储解析结果 ----------------------
private DeliveryHeader deliveryHeader = new DeliveryHeader();
private List<ProductDetail> productDetailList = new ArrayList<>();
private List<CustomerDeliveryDetail> customerDeliveryDetailList = new ArrayList<>();
private final DeliveryHeader deliveryHeader = new DeliveryHeader();
private final List<ProductDetail> productDetailList = new ArrayList<>();
private final List<CustomerDeliveryDetail> customerDeliveryDetailList = new ArrayList<>();
// ---------------------- 解析状态管理动态切换 ----------------------
public enum ParseStatus {
IDLE, // 空闲状态未进入任何表格
PRODUCT_DETAIL,// 货品明细解析状态
CUSTOMER_DETAIL// 客户配送明细解析状态
}
private ParseStatus currentStatus = ParseStatus.IDLE;
// 当前行号1-based与Excel行号一致
private int currentRowNum = 0;
// ---------------------- 核心解析方法EasyExcel回调 ----------------------
@Override
public void invoke(Map<String, Object> data, AnalysisContext context) {
currentRowNum = context.readRowHolder().getRowIndex() + 1;
try {
int rowNum = context.readRowHolder().getRowIndex() + 1;
LOGGER.debug("第{}行:开始解析,所有单元格数据:{}", rowNum, data);
LOGGER.debug("第{}行:开始解析,所有单元格数据:{}", currentRowNum, data);
// 1. 获取第一列数据用于状态判断标签通常在第一
String firstCellValue = getFirstCellValue(data);
LOGGER.debug("第{}行:第一列数据(状态判断依据):{}", rowNum, firstCellValue);
// 左侧区域解析A-E
parseLeftSection(data);
// 2. 状态切换判断传递完整data支持多单元格解析
if (isStatusSwitchTrigger(firstCellValue)) {
switchStatus(firstCellValue, rowNum, data);
return;
}
// 3. 根据当前状态解析对应表格数据
switch (currentStatus) {
case PRODUCT_DETAIL:
if (isValidProductDetailRow(data, firstCellValue)) {
parseProductDetail(data, rowNum);
}
break;
case CUSTOMER_DETAIL:
if (isValidCustomerDetailRow(data, firstCellValue)) {
parseCustomerDeliveryDetail(data, rowNum);
}
break;
case IDLE:
// 空闲状态解析表头信息送货日期行多单元格数据
if (firstCellValue.contains(KEYWORD_DELIVERY_DATE)) {
parseHeaderInfo(data, rowNum);
}
break;
}
// 右侧区域解析F-M列
parseRightSection(data);
} catch (ExcelDataConvertException e) {
// Excel内置转换异常如日期格式错误
int rowNum = context.readRowHolder().getRowIndex() + 1;
LOGGER.error("第{}行:Excel数据转换异常,列号:{},列名:{}",
rowNum, e.getColumnIndex() + 1, getColumnNameByIndex(data, e.getColumnIndex()), e);
throw new RuntimeException("Excel数据转换异常(行:" + rowNum + ",列:" + (e.getColumnIndex() + 1) + "", e);
currentRowNum, e.getColumnIndex() + 1, getColumnNameByIndex(data, e.getColumnIndex()), e);
throw new RuntimeException("Excel数据转换异常(行:" + currentRowNum + ",列:" + (e.getColumnIndex() + 1) + "", e);
} catch (Exception e) {
// 其他解析异常
int rowNum = context.readRowHolder().getRowIndex() + 1;
LOGGER.error("第{}行:Excel解析异常,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("Excel解析异常(行:" + rowNum + "", e);
LOGGER.error("第{}行:Excel解析异常,所有单元格数据:{}", currentRowNum, data, e);
throw new RuntimeException("Excel解析异常(行:" + currentRowNum + "", e);
}
}
// ---------------------- 左侧区域解析货品相关信息 ----------------------
private void parseLeftSection(Map<String, Object> data) {
switch (currentRowNum) {
case 1:
// 第1行送货日期A列=标签B列=
parseDeliveryDate(data);
break;
case 2:
// 第2行送货线号A列=标签B列=
parseDeliveryLineNo(data);
break;
case 3:
// 第3行车次A列=标签B列=
parseTrainNumber(data);
break;
case 4:
// 第4行货品明细标题行无需解析
LOGGER.debug("第{}行:货品明细标题行,跳过", currentRowNum);
break;
case 5:
// 第5行纪录合共A列=标签B列=总记录数D列=总箱数E列=总支数
parseTotalRecord(data);
break;
case 6:
// 第6行货品明细列标题貨品編號/簡稱//跳过
LOGGER.debug("第{}行:货品明细列标题行,跳过", currentRowNum);
break;
default:
// 第7行起货品明细数据行遇到空行自动结束
if (currentRowNum >= 7 && isValidProductDetailRow(data)) {
parseProductDetail(data, currentRowNum);
}
break;
}
}
// ---------------------- 右侧区域解析客户配送相关信息 ----------------------
private void parseRightSection(Map<String, Object> data) {
switch (currentRowNum) {
case 1:
// 第1行客户配送明细标题行无需解析
LOGGER.debug("第{}行:客户配送明细标题行,跳过", currentRowNum);
break;
case 2:
// 第2行发票张数/小计F列=發票張數标签G列=数值J列=小計标签K/L/M列=数值
parseInvoiceCount(data);
break;
case 3:
// 第3行客户配送明细列标题類别/發票編號/客户編號/名稱///金額跳过
LOGGER.debug("第{}行:客户配送明细列标题行,跳过", currentRowNum);
break;
default:
// 第4行起客户配送明细数据行遇到空行自动结束
if (currentRowNum >= 4 && isValidCustomerDetailRow(data)) {
parseCustomerDeliveryDetail(data, currentRowNum);
}
break;
}
}
@@ -116,8 +133,10 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
LOGGER.info("Excel解析全部完成!最终统计结果:");
LOGGER.info("1. 表头信息:送货日期={},发票张数={},总记录数={},小计金额={}",
LOGGER.info("1. 表头信息:送货日期={}送货线号={},车次={}发票张数={},总记录数={},小计金额={}",
deliveryHeader.getDeliveryDate(),
deliveryHeader.getDeliveryLineNo(),
deliveryHeader.getTrainNumber(),
deliveryHeader.getInvoiceCount(),
deliveryHeader.getTotalRecordCount(),
deliveryHeader.getSubtotalAmount());
@@ -125,253 +144,185 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
LOGGER.info("3. 客户配送明细:共{}条记录", customerDeliveryDetailList.size());
}
// ---------------------- 状态管理方法传递完整data ----------------------
/**
* 判断是否触发状态切换根据第一列标签关键字
*/
private boolean isStatusSwitchTrigger(String firstCellValue) {
return firstCellValue.contains(KEYWORD_PRODUCT_DETAIL)
|| firstCellValue.contains(KEYWORD_TOTAL_RECORD)
|| firstCellValue.contains(KEYWORD_CUSTOMER_DETAIL)
|| firstCellValue.contains(KEYWORD_INVOICE_COUNT);
}
/**
* 切换解析状态核心传递完整data支持多单元格解析
*/
private void switchStatus(String firstCellValue, int rowNum, Map<String, Object> data) {
if (firstCellValue.contains(KEYWORD_PRODUCT_DETAIL)) {
currentStatus = ParseStatus.PRODUCT_DETAIL;
LOGGER.info("第{}行:检测到【{}】标签,切换到【货品明细】解析状态", rowNum, KEYWORD_PRODUCT_DETAIL);
} else if (firstCellValue.contains(KEYWORD_TOTAL_RECORD)) {
currentStatus = ParseStatus.IDLE;
parseTotalRecord(data, rowNum); // 解析纪录合共多单元格
LOGGER.info("第{}行:检测到【{}】标签,切换到【空闲】状态,已解析货品明细{}条",
rowNum, KEYWORD_TOTAL_RECORD, productDetailList.size());
} else if (firstCellValue.contains(KEYWORD_CUSTOMER_DETAIL)) {
currentStatus = ParseStatus.CUSTOMER_DETAIL;
LOGGER.info("第{}行:检测到【{}】标签,切换到【客户配送明细】解析状态", rowNum, KEYWORD_CUSTOMER_DETAIL);
} else if (firstCellValue.contains(KEYWORD_INVOICE_COUNT)) {
currentStatus = ParseStatus.IDLE;
parseInvoiceCount(data, rowNum); // 解析发票张数多单元格核心修复
LOGGER.info("第{}行:检测到【{}】标签,切换到【空闲】状态,已解析客户配送明细{}条",
rowNum, KEYWORD_INVOICE_COUNT, customerDeliveryDetailList.size());
// ---------------------- 左侧表头信息解析 ----------------------
private void parseDeliveryDate(Map<String, Object> data) {
Object value = getValueSafely(data, 1); // B列
if (value != null) {
Date deliveryDate = convertToDate(value);
deliveryHeader.setDeliveryDate(deliveryDate);
LOGGER.debug("第{}行:送货日期解析完成,值={}", currentRowNum, deliveryDate);
}
}
// ---------------------- 数据有效性校验方法 ----------------------
private void parseDeliveryLineNo(Map<String, Object> data) {
Object value = getValueSafely(data, 1); // B列
if (value != null) {
String deliveryLineNo = value.toString().trim();
deliveryHeader.setDeliveryLineNo(deliveryLineNo);
LOGGER.debug("第{}行:送货线号解析完成,值={}", currentRowNum, deliveryLineNo);
}
}
private void parseTrainNumber(Map<String, Object> data) {
Object value = getValueSafely(data, 1); // B列
Integer trainNumber = convertToInteger(value);
deliveryHeader.setTrainNumber(trainNumber);
LOGGER.debug("第{}行:车次解析完成,值={}", currentRowNum, trainNumber);
}
// ---------------------- 左侧纪录合共解析 ----------------------
/**
* 第5行纪录合共
* 列对应关系0=A列=紀錄合共标签1=B列=总记录数3=D列=总箱数4=E列=总支数
*/
private void parseTotalRecord(Map<String, Object> data) {
Object totalRecordLabel = getValueSafely(data, 0);
if (totalRecordLabel == null || !totalRecordLabel.toString().trim().contains(KEYWORD_TOTAL_RECORD)) {
LOGGER.warn("第{}行:不是纪录合共行,跳过解析,第一列内容:{}", currentRowNum, totalRecordLabel);
return;
}
Integer totalRecordCount = convertToInteger(getValueSafely(data, 1)); // B列
Integer totalBoxCount = convertToInteger(getValueSafely(data, 3)); // D列
Integer totalPieceCount = convertToInteger(getValueSafely(data, 4)); // E列
deliveryHeader.setTotalRecordCount(totalRecordCount);
deliveryHeader.setTotalBoxCount(totalBoxCount);
deliveryHeader.setTotalPieceCount(totalPieceCount);
LOGGER.debug("第{}行:解析纪录合共完成,总记录数={},总箱数={},总支数={}",
currentRowNum, totalRecordCount, totalBoxCount, totalPieceCount);
}
// ---------------------- 左侧货品明细解析 ----------------------
/**
* 校验货品明细行是否有效排除表头和空行
*/
private boolean isValidProductDetailRow(Map<String, Object> data, String firstCellValue) {
// 有效条件1.货品编号第一列不为空 2.不是表头行表头第一列是"貨品編號"
Object productCode = getValueSafely(data, 0);
private boolean isValidProductDetailRow(Map<String, Object> data) {
Object productCode = getValueSafely(data, 0); // A列
return productCode != null
&& !"貨品編號".equals(productCode.toString().trim())
&& !productCode.toString().trim().isEmpty();
&& !productCode.toString().trim().isEmpty()
&& !"貨品編號".equals(productCode.toString().trim());
}
/**
* 解析货品明细第7行起
* 列对应关系0=A列=貨品編號1=B列=簡稱2=C列=3=D列=
*/
private void parseProductDetail(Map<String, Object> data, int rowNum) {
ProductDetail productDetail = new ProductDetail();
productDetail.setProductCode(convertToString(getValueSafely(data, 0)));
productDetail.setProductName(convertToString(getValueSafely(data, 1)));
productDetail.setBoxCount(convertToInteger(getValueSafely(data, 2)));
productDetail.setPieceCount(convertToInteger(getValueSafely(data, 3)));
productDetailList.add(productDetail);
LOGGER.debug("第{}行:解析货品明细完成,货品编号={},名称={},箱数={},支数={}",
rowNum, productDetail.getProductCode(), productDetail.getProductName(),
productDetail.getBoxCount(), productDetail.getPieceCount());
}
// ---------------------- 右侧发票张数与小计解析 ----------------------
/**
* 第2行发票张数与小计
* 通过关键字定位标签再读取右侧相邻列避免空列导致索引偏移
*/
private void parseInvoiceCount(Map<String, Object> data) {
// 1. 定位并解析发票张数
Integer invoiceCount = findValueAfterKeyword(data, KEYWORD_INVOICE_COUNT);
if (invoiceCount != null) {
deliveryHeader.setInvoiceCount(invoiceCount);
}
// 2. 定位并解析小计标签右侧第1列=第2列=第3列=金额
Integer subtotalStartIndex = findKeywordIndex(data, KEYWORD_SUBTOTAL);
if (subtotalStartIndex != null && subtotalStartIndex >= 0) {
Integer subtotalBoxCount = convertToInteger(getValueSafely(data, subtotalStartIndex + 1));
Integer subtotalPieceCount = convertToInteger(getValueSafely(data, subtotalStartIndex + 2));
BigDecimal subtotalAmount = convertToBigDecimal(getValueSafely(data, subtotalStartIndex + 3));
deliveryHeader.setSubtotalBoxCount(subtotalBoxCount);
deliveryHeader.setSubtotalPieceCount(subtotalPieceCount);
deliveryHeader.setSubtotalAmount(subtotalAmount);
LOGGER.debug("第{}行:解析小计完成,箱数={},支数={},金额={}",
currentRowNum, subtotalBoxCount, subtotalPieceCount, subtotalAmount);
} else {
LOGGER.warn("第{}行:未找到小计标签,小计信息置空", currentRowNum);
deliveryHeader.setSubtotalBoxCount(0);
deliveryHeader.setSubtotalPieceCount(0);
deliveryHeader.setSubtotalAmount(BigDecimal.ZERO);
}
LOGGER.debug("第{}行:解析发票张数行完成,发票张数={},小计金额={}",
currentRowNum, deliveryHeader.getInvoiceCount(), deliveryHeader.getSubtotalAmount());
}
// ---------------------- 右侧客户配送明细解析 ----------------------
/**
* 校验客户配送明细行是否有效排除表头和空行
*/
private boolean isValidCustomerDetailRow(Map<String, Object> data, String firstCellValue) {
// 有效条件1.类别第一列不为空 2.不是表头行表头第一列是"類别"
Object category = getValueSafely(data, 0);
private boolean isValidCustomerDetailRow(Map<String, Object> data) {
Object category = getValueSafely(data, 5); // F列
return category != null
&& !"類别".equals(category.toString().trim())
&& !category.toString().trim().isEmpty();
}
// ---------------------- 具体数据解析方法全部支持多单元格 ----------------------
/**
* 解析表头信息送货日期送货线号车次
* 列对应关系0=送貨日期标签1=送貨日期值2=送貨線號标签3=送貨線號值4=車次标签5=車次值
*/
private void parseHeaderInfo(Map<String, Object> data, int rowNum) {
try {
// 读取多单元格数据标签在左数值在右
Date deliveryDate = getValueSafely(data, 1, Date.class); // 第2列送货日期值
String deliveryLineNo = getValueSafely(data, 3, String.class); // 第4列送货线号值
Integer trainNumber = getValueSafely(data, 5, Integer.class); // 第6列车次值
// 赋值空值处理
if (deliveryDate != null) {
deliveryHeader.setDeliveryDate(deliveryDate);
}
deliveryHeader.setDeliveryLineNo(deliveryLineNo);
deliveryHeader.setTrainNumber(getDefaultValue(trainNumber, 0));
LOGGER.debug("第{}行:解析表头信息完成,送货日期={},送货线号={},车次={}",
rowNum, deliveryHeader.getDeliveryDate(),
deliveryHeader.getDeliveryLineNo(), deliveryHeader.getTrainNumber());
} catch (Exception e) {
LOGGER.error("第{}行:解析表头信息失败,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("解析表头信息失败(行:" + rowNum + "", e);
}
&& !category.toString().trim().isEmpty()
&& !"類别".equals(category.toString().trim());
}
/**
* 解析纪录合共多单元格版
* 列对应关系0=紀錄合共标签1=总记录数值4=總計标签5=总箱数值6=总支数值
*/
private void parseTotalRecord(Map<String, Object> data, int rowNum) {
try {
// 1. 验证是否为纪录合共行第一列包含标签
Object totalRecordLabel = getValueSafely(data, 0, String.class);
if (totalRecordLabel == null || !totalRecordLabel.toString().trim().contains(KEYWORD_TOTAL_RECORD)) {
LOGGER.warn("第{}行:不是纪录合共行,跳过解析,第一列内容:{}", rowNum, totalRecordLabel);
return;
}
// 2. 读取多单元格数值标签右侧对应列
Integer totalRecordCount = getValueSafely(data, 1, Integer.class); // 第2列总记录数
Integer totalBoxCount = getValueSafely(data, 5, Integer.class); // 第6列总箱数
Integer totalPieceCount = getValueSafely(data, 6, Integer.class); // 第7列总支数
// 3. 赋值与日志
deliveryHeader.setTotalRecordCount(getDefaultValue(totalRecordCount, 0));
deliveryHeader.setTotalBoxCount(getDefaultValue(totalBoxCount, 0));
deliveryHeader.setTotalPieceCount(getDefaultValue(totalPieceCount, 0));
LOGGER.debug("第{}行:解析纪录合共完成,总记录数={},总箱数={},总支数={}",
rowNum, deliveryHeader.getTotalRecordCount(),
deliveryHeader.getTotalBoxCount(), deliveryHeader.getTotalPieceCount());
} catch (Exception e) {
LOGGER.error("第{}行:解析纪录合共失败,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("解析纪录合共失败(行:" + rowNum + "", e);
}
}
/**
* 核心修复解析发票张数多单元格版正确读取右侧单元格
* 列对应关系
* - 0=發票張數标签列A列1=发票张数数值列B列
* - 4=小計标签列E列5=小计箱数列F列6=小计支数列G列7=小计金额列H列
*/
private void parseInvoiceCount(Map<String, Object> data, int rowNum) {
try {
// 1. 验证是否为发票张数行第一列包含标签
Object invoiceLabel = getValueSafely(data, 0, String.class);
if (invoiceLabel == null || !invoiceLabel.toString().trim().contains(KEYWORD_INVOICE_COUNT)) {
LOGGER.warn("第{}行:不是发票张数行,跳过解析,第一列内容:{}", rowNum, invoiceLabel);
return;
}
// 2. 解析发票张数标签右侧第1列B列索引1
Integer invoiceCount = getValueSafely(data, 1, Integer.class);
deliveryHeader.setInvoiceCount(getDefaultValue(invoiceCount, 0));
LOGGER.debug("第{}行:解析发票张数,标签列(A列):{},数值列(B列):{}",
rowNum, invoiceLabel, deliveryHeader.getInvoiceCount());
// 3. 解析小计信息标签在E列数值在F/G/H列
Object subtotalLabel = getValueSafely(data, 3, String.class); // E列小計标签
if (subtotalLabel != null && subtotalLabel.toString().trim().contains(KEYWORD_SUBTOTAL)) {
Integer subtotalBoxCount = getValueSafely(data, 4, Integer.class); // F列小计箱数
Integer subtotalPieceCount = getValueSafely(data, 5, Integer.class); // G列小计支数
BigDecimal subtotalAmount = getValueSafely(data, 6, BigDecimal.class); // H列小计金额
// 赋值空值处理
deliveryHeader.setSubtotalBoxCount(getDefaultValue(subtotalBoxCount, 0));
deliveryHeader.setSubtotalPieceCount(getDefaultValue(subtotalPieceCount, 0));
deliveryHeader.setSubtotalAmount(getDefaultValue(subtotalAmount, BigDecimal.ZERO));
LOGGER.debug("第{}行:解析小计信息,标签列(E列):{},箱数列(F列):{},支数列(G列):{},金额列(H列):{}",
rowNum, subtotalLabel,
deliveryHeader.getSubtotalBoxCount(),
deliveryHeader.getSubtotalPieceCount(),
deliveryHeader.getSubtotalAmount());
} else {
LOGGER.warn("第{}行:未找到小计标签(E列),或标签内容不匹配,跳过小计解析", rowNum);
deliveryHeader.setSubtotalBoxCount(0);
deliveryHeader.setSubtotalPieceCount(0);
deliveryHeader.setSubtotalAmount(BigDecimal.ZERO);
}
// 4. 解析完成日志
LOGGER.debug("第{}行:解析发票张数行完成,最终结果:发票张数={},小计金额={}",
rowNum, deliveryHeader.getInvoiceCount(), deliveryHeader.getSubtotalAmount());
} catch (Exception e) {
LOGGER.error("第{}行:解析发票张数失败,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("解析发票张数失败(行:" + rowNum + "),请检查Excel列对应关系", e);
}
}
/**
* 解析货品明细多单元格动态行数适配
* 列对应关系0=貨品編號1=簡稱4=5=
*/
private void parseProductDetail(Map<String, Object> data, int rowNum) {
try {
ProductDetail productDetail = new ProductDetail();
// 读取对应列的多单元格数据
String productCode = getValueSafely(data, 0, String.class); // 第1列货品编号
String productName = getValueSafely(data, 1, String.class); // 第2列货品简称
Integer boxCount = getValueSafely(data, 4, Integer.class); // 第5列箱数
Integer pieceCount = getValueSafely(data, 5, Integer.class); // 第6列支数
// 赋值空值处理+去空格
productDetail.setProductCode(getDefaultValue(productCode, "").trim());
productDetail.setProductName(getDefaultValue(productName, "").trim());
productDetail.setBoxCount(getDefaultValue(boxCount, 0));
productDetail.setPieceCount(getDefaultValue(pieceCount, 0));
// 添加到列表
productDetailList.add(productDetail);
LOGGER.debug("第{}行:解析货品明细完成,货品编号={},名称={},箱数={},支数={}",
rowNum, productDetail.getProductCode(), productDetail.getProductName(),
productDetail.getBoxCount(), productDetail.getPieceCount());
} catch (Exception e) {
LOGGER.error("第{}行:解析货品明细失败,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("解析货品明细失败(行:" + rowNum + "", e);
}
}
/**
* 解析客户配送明细多单元格动态行数适配
* 列对应关系0=類别1=發票編號2=客户編號3=名稱4=5=6=金額
* 解析客户配送明细第4行起
* 列对应关系5=F列=類别6=G列=發票編號7=H列=客户編號8=I列=名稱
* 9=J列=10=K列=11=L列=金額
*/
private void parseCustomerDeliveryDetail(Map<String, Object> data, int rowNum) {
try {
CustomerDeliveryDetail customerDetail = new CustomerDeliveryDetail();
// 读取对应列的多单元格数据
String category = getValueSafely(data, 0, String.class); // 第1列类别
String invoiceNo = getValueSafely(data, 1, String.class); // 第2列发票编号
String customerCode = getValueSafely(data, 2, String.class); // 第3列客户编号
String customerName = getValueSafely(data, 3, String.class); // 第4列客户名称
Integer boxCount = getValueSafely(data, 4, Integer.class); // 第5列箱数
Integer packageCount = getValueSafely(data, 5, Integer.class); // 第6列包数
BigDecimal amount = getValueSafely(data, 6, BigDecimal.class); // 第7列金额
CustomerDeliveryDetail customerDetail = new CustomerDeliveryDetail();
customerDetail.setCategory(convertToString(getValueSafely(data, 5)));
customerDetail.setInvoiceNo(convertToString(getValueSafely(data, 6)));
customerDetail.setCustomerCode(convertToString(getValueSafely(data, 7)));
customerDetail.setCustomerName(convertToString(getValueSafely(data, 8)));
customerDetail.setBoxCount(convertToInteger(getValueSafely(data, 9)));
customerDetail.setPackageCount(convertToInteger(getValueSafely(data, 10)));
customerDetail.setAmount(convertToBigDecimal(getValueSafely(data, 11)));
// 赋值空值处理+去空格
customerDetail.setCategory(getDefaultValue(category, "").trim());
customerDetail.setInvoiceNo(getDefaultValue(invoiceNo, "").trim());
customerDetail.setCustomerCode(getDefaultValue(customerCode, "").trim());
customerDetail.setCustomerName(getDefaultValue(customerName, "").trim());
customerDetail.setBoxCount(getDefaultValue(boxCount, 0));
customerDetail.setPackageCount(getDefaultValue(packageCount, 0));
customerDetail.setAmount(getDefaultValue(amount, BigDecimal.ZERO));
// 添加到列表
customerDeliveryDetailList.add(customerDetail);
LOGGER.debug("第{}行:解析客户配送明细完成,客户编号={},名称={},金额={}",
rowNum, customerDetail.getCustomerCode(), customerDetail.getCustomerName(),
customerDetail.getAmount());
} catch (Exception e) {
LOGGER.error("第{}行:解析客户配送明细失败,所有单元格数据:{}", rowNum, data, e);
throw new RuntimeException("解析客户配送明细失败(行:" + rowNum + "", e);
}
customerDeliveryDetailList.add(customerDetail);
LOGGER.debug("第{}行:解析客户配送明细完成,类别={},发票编号={},客户编号={},名称={},箱数={},包数={},金额={}",
rowNum, customerDetail.getCategory(), customerDetail.getInvoiceNo(),
customerDetail.getCustomerCode(), customerDetail.getCustomerName(),
customerDetail.getBoxCount(), customerDetail.getPackageCount(), customerDetail.getAmount());
}
// ---------------------- 核心工具方法支持多单元格+类型转换 ----------------------
/**
* 获取第一列数据用于状态判断
* 根据列索引安全获取值
* 优先按Map的key直接查找适配EasyExcel以列号为key的情况找不到再按位置顺序查找
* 注意EasyExcel在某些情况下如合并单元格headRowNumber=0可能将表头文本作为key
* 此时key不是数字字符串需跳过这些key走位置兜底逻辑
*/
private String getFirstCellValue(Map<String, Object> data) {
private Object getValueSafely(Map<String, Object> data, int columnIndex) {
if (data == null || data.isEmpty()) {
return "";
return null;
}
Object firstValue = data.values().iterator().next();
return firstValue != null ? firstValue.toString().trim() : "";
String strIndex = String.valueOf(columnIndex);
// 1. 直接按key查找兼容key为String或Integer的情况跳过非数字key
for (Object keyObj : data.keySet()) {
if (keyObj != null && strIndex.equals(keyObj.toString())) {
return data.get(keyObj);
}
}
// 2. 兜底按顺序位置查找保持与原逻辑兼容
int index = 0;
for (Object value : data.values()) {
if (index == columnIndex) {
return value;
}
index++;
}
LOGGER.warn("第{}行列索引{}超出当前行单元格数量({}列),返回null",
currentRowNum, columnIndex, data.size());
return null;
}
/**
@@ -392,115 +343,121 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
}
/**
* 按列索引安全获取原始值不做类型转换
* 在指定行中查找关键字所在的列索引
* 注意Map的key可能是非数字字符串如表头文本"客户配送明细"此时跳过该key不抛出异常
*/
private Object getValueSafely(Map<String, Object> data, int columnIndex) {
if (data == null || data.isEmpty()) {
private Integer findKeywordIndex(Map<String, Object> data, String keyword) {
if (data == null || data.isEmpty() || keyword == null) {
return null;
}
int index = 0;
for (Object value : data.values()) {
if (index == columnIndex) {
return value;
for (Object keyObj : data.keySet()) {
Object value = data.get(keyObj);
if (value != null && value.toString().trim().contains(keyword)) {
try {
return Integer.parseInt(keyObj.toString());
} catch (NumberFormatException e) {
// key不是数字可能是表头文本如"客户配送明细"跳过不打印警告避免日志刷屏
LOGGER.debug("第{}行:关键字'{}'所在列key不是数字,key={},跳过",
currentRowNum, keyword, keyObj);
}
}
index++;
}
LOGGER.warn("列索引{}超出当前行单元格数量({}列),返回null", columnIndex, data.size());
return null;
}
/**
* 按列索引安全获取支持多类型转换核心工具
* 功能1. 直接类型匹配 2. String转数字/日期 3. 数字类型间转换
* 查找关键字所在列的右侧相邻列并转为Integer
*/
private <T> T getValueSafely(Map<String, Object> data, int columnIndex, Class<T> expectedType) {
Object value = getValueSafely(data, columnIndex);
if (value == null) {
LOGGER.debug("列索引{}:单元格值为null,返回null", columnIndex);
private Integer findValueAfterKeyword(Map<String, Object> data, String keyword) {
Integer keywordIndex = findKeywordIndex(data, keyword);
if (keywordIndex == null) {
LOGGER.warn("第{}行:未找到关键字'{}'", currentRowNum, keyword);
return null;
}
return convertToInteger(getValueSafely(data, keywordIndex + 1));
}
// 1. 直接类型匹配
if (expectedType.isInstance(value)) {
return expectedType.cast(value);
/**
* Object转String
*/
private String convertToString(Object value) {
return value == null ? "" : value.toString().trim();
}
/**
* Object转Integer支持文本型数字Number类型
*/
private Integer convertToInteger(Object value) {
if (value == null) {
return 0;
}
if (value instanceof Number) {
return ((Number) value).intValue();
}
// 2. String类型转目标类型核心处理文本型数字
if (value instanceof String) {
String strValue = ((String) value).trim();
if (strValue.isEmpty()) {
LOGGER.debug("列索引{}:单元格值为空字符串,返回null", columnIndex);
return null;
return 0;
}
// String Integer
if (expectedType == Integer.class) {
try {
return expectedType.cast(Integer.parseInt(strValue));
} catch (NumberFormatException e) {
LOGGER.warn("列索引{}String转Integer失败,原始值:{}", columnIndex, strValue, e);
return null;
}
}
// String Long
if (expectedType == Long.class) {
try {
return expectedType.cast(Long.parseLong(strValue));
} catch (NumberFormatException e) {
LOGGER.warn("列索引{}String转Long失败,原始值:{}", columnIndex, strValue, e);
return null;
}
}
// String BigDecimal支持千分位符号"1,234.56"
if (expectedType == BigDecimal.class) {
try {
String cleanValue = strValue.replace(",", ""); // 去除千分位逗号
return expectedType.cast(new BigDecimal(cleanValue));
} catch (NumberFormatException e) {
LOGGER.warn("列索引{}String转BigDecimal失败,原始值:{}", columnIndex, strValue, e);
return null;
}
}
// String Date支持多种日期格式
if (expectedType == Date.class) {
try {
return expectedType.cast(DateUtils.parseDate(strValue, SUPPORTED_DATE_FORMATS));
} catch (ParseException e) {
LOGGER.warn("列索引{}String转Date失败,原始值:{},支持格式:{}",
columnIndex, strValue, Arrays.toString(SUPPORTED_DATE_FORMATS), e);
return null;
}
try {
return Integer.parseInt(strValue);
} catch (NumberFormatException e) {
LOGGER.warn("第{}行:值转换为Integer失败,值:{},返回0", currentRowNum, value, e);
}
}
// 3. 数字类型间转换如DoubleIntegerLongBigDecimal
if (value instanceof Number) {
Number numValue = (Number) value;
if (expectedType == Integer.class) {
return expectedType.cast(numValue.intValue());
}
if (expectedType == Long.class) {
return expectedType.cast(numValue.longValue());
}
if (expectedType == BigDecimal.class) {
return expectedType.cast(new BigDecimal(numValue.toString()));
}
}
// 4. 不支持的转换
LOGGER.warn("列索引{}:不支持的类型转换,期望类型:{},实际类型:{},原始值:{}",
columnIndex, expectedType.getSimpleName(), value.getClass().getSimpleName(), value);
return null;
return 0;
}
/**
* 空值处理返回默认值
* Object转BigDecimal支持千分位符号"1,234.56"
*/
private <T> T getDefaultValue(T value, T defaultValue) {
return value != null ? value : defaultValue;
private BigDecimal convertToBigDecimal(Object value) {
if (value == null) {
return BigDecimal.ZERO;
}
if (value instanceof BigDecimal) {
return (BigDecimal) value;
}
if (value instanceof Number) {
return new BigDecimal(value.toString());
}
if (value instanceof String) {
String strValue = ((String) value).trim().replace(",", "");
if (strValue.isEmpty()) {
return BigDecimal.ZERO;
}
try {
return new BigDecimal(strValue);
} catch (NumberFormatException e) {
LOGGER.warn("第{}行:值转换为BigDecimal失败,值:{},返回0", currentRowNum, value, e);
}
}
return BigDecimal.ZERO;
}
/**
* Object转Date支持Date类型String类型
*/
private Date convertToDate(Object value) {
if (value == null) {
return null;
}
if (value instanceof Date) {
return (Date) value;
}
if (value instanceof String) {
String strValue = ((String) value).trim();
if (strValue.isEmpty()) {
return null;
}
try {
return DateUtils.parseDate(strValue, SUPPORTED_DATE_FORMATS);
} catch (ParseException e) {
LOGGER.warn("第{}行:值转换为Date失败,值:{},支持格式:{}",
currentRowNum, strValue, Arrays.toString(SUPPORTED_DATE_FORMATS), e);
}
}
return null;
}
// ---------------------- Getter方法供Service层获取解析结果 ----------------------
@@ -515,4 +472,4 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
public List<CustomerDeliveryDetail> getCustomerDeliveryDetailList() {
return customerDeliveryDetailList;
}
}
}
@@ -17,7 +17,7 @@ import java.util.Map;
/**
* 入库单Excel解析监听器
* 功能1. 解析第一行表头数据 2. 解析第行开始的产品明细 3. 支持文本型数字转换
* 功能1. 解析第一行表头数据 2. 跳过第二行合计行第三行列名行 3. 解析第行开始的产品明细 4. 支持文本型数字转换
*/
public class StockInExcelListener extends AnalysisEventListener<Map<String, Object>> {
private static final Logger LOGGER = LoggerFactory.getLogger(StockInExcelListener.class);
@@ -44,8 +44,16 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
if (currentRowNum == 1) {
parseHeaderData(data);
}
// 2. 解析第二行及以后产品明细跳过表头行第2行是表格列名
else if (currentRowNum >= 3) {
// 2. 跳过第二行合计行Total: xxx
else if (currentRowNum == 2) {
LOGGER.debug("第{}行是合计行,跳过解析", currentRowNum);
}
// 3. 跳过第三行表格列名行產品編號產品名稱...
else if (currentRowNum == 3) {
LOGGER.debug("第{}行是表格列名行,跳过解析", currentRowNum);
}
// 4. 解析第四行及以后产品明细
else if (currentRowNum >= 4) {
if (isValidProductRow(data)) {
StockInProductDetail detail = parseProductDetail(data);
productDetailList.add(detail);
@@ -66,19 +74,19 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
/**
* 解析第一行表头数据
* 列对应关系0=入库日期标签1=入库日期值2=OrderNO标签3=OrderNO值
* 模板中标签与值相邻分别搜索"入库日期""OrderNO"标签后一列的值兼容位置调整
*/
private void parseHeaderData(Map<String, Object> data) {
// 入库日期第2列索引1
Object stockInDateObj = getValueSafely(data, 1);
// 入库日期查找包含"入库日期"标签的下一个值
Object stockInDateObj = getValueAfterLabel(data, "入库日期");
if (stockInDateObj != null) {
Date stockInDate = convertToDate(stockInDateObj);
stockInHeader.setStockInDate(stockInDate);
LOGGER.debug("第1行:入库日期解析完成,值:{}", stockInDate);
}
// OrderNO第4列索引3
Object orderNoObj = getValueSafely(data, 3);
// OrderNO查找包含"OrderNO"标签的下一个值
Object orderNoObj = getValueAfterLabel(data, "OrderNO");
if (orderNoObj != null) {
String orderNo = orderNoObj.toString().trim();
stockInHeader.setOrderNo(orderNo);
@@ -86,7 +94,7 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
}
// 校验表头数据完整性
if (stockInHeader.getStockInDate() == null || stockInHeader.getOrderNo().isEmpty()) {
if (stockInHeader.getStockInDate() == null || stockInHeader.getOrderNo() == null || stockInHeader.getOrderNo().isEmpty()) {
LOGGER.warn("第1行表头数据不完整,入库日期:{},OrderNO:{}",
stockInHeader.getStockInDate(), stockInHeader.getOrderNo());
}
@@ -94,22 +102,23 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
/**
* 解析产品明细数据一行对应一个产品
* 新模板列顺序0=产品编号1=产品名称2=规格箱含量3=件数4=Qty(cases)箱数5=饮用日期
*/
private StockInProductDetail parseProductDetail(Map<String, Object> data) {
StockInProductDetail detail = new StockInProductDetail();
// 产品编号第1列索引0- 支持文本型数字转换
detail.setProductCode(convertToString(getValueSafely(data, 0)).toString());
detail.setProductCode(convertToString(getValueSafely(data, 0)));
// 产品名称第2列索引1
detail.setProductName(convertToString(getValueSafely(data, 1)));
// 箱含量5索引4
detail.setBoxCapacity(convertToInteger(getValueSafely(data, 4)));
// 件数6索引5
detail.setPieceCount(convertToInteger(getValueSafely(data, 5)));
// 箱数7索引6
detail.setBoxCount(convertToInteger(getValueSafely(data, 6)));
// 饮用日期8索引7
detail.setDrinkDate(convertToDate(getValueSafely(data, 7)));
// 箱含量/规格3索引2
detail.setBoxCapacity(convertToInteger(getValueSafely(data, 2)));
// 件数4索引3
detail.setPieceCount(convertToInteger(getValueSafely(data, 3)));
// 箱数/Qty(cases)5索引4
detail.setBoxCount(convertToInteger(getValueSafely(data, 4)));
// 饮用日期6索引5
detail.setDrinkDate(convertToDate(getValueSafely(data, 5)));
LOGGER.debug("第{}行产品明细解析完成,产品编号:{},产品名称:{}",
currentRowNum, detail.getProductCode(), detail.getProductName());
@@ -120,12 +129,11 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
* 校验产品行是否有效排除空行和合计行
*/
private boolean isValidProductRow(Map<String, Object> data) {
// 有效条件产品编号不为空 + 不是合计行合计行产品编号为null且包含"Total"
// 有效条件产品编号不为空 + 不是合计行
Object productCodeObj = getValueSafely(data, 0);
Object productNameObj = getValueSafely(data, 1);
// 排除合计行如第7行Total68
if (productNameObj != null && productNameObj.toString().contains("Total")) {
// 排除合计行整行包含"Total"字样
if (containsAnyValue(data, "Total")) {
LOGGER.debug("第{}行是合计行,跳过解析", currentRowNum);
return false;
}
@@ -186,6 +194,40 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
return "" + (columnIndex + 1) + "";
}
/**
* 根据标签查找相邻的下一个值用于第一行表头兼容标签位置调整
*/
private Object getValueAfterLabel(Map<String, Object> data, String label) {
if (data == null || data.isEmpty()) {
return null;
}
Object prevValue = null;
for (Object value : data.values()) {
if (prevValue != null && prevValue.toString().contains(label)) {
return value;
}
prevValue = value;
}
return null;
}
/**
* 判断当前行是否有任意单元格包含指定文本
*/
private boolean containsAnyValue(Map<String, Object> data, String text) {
if (data == null || data.isEmpty()) {
return false;
}
for (Object value : data.values()) {
if (value != null && value.toString().contains(text)) {
return true;
}
}
return false;
}
/**
* 通用转换Object转String
*/
@@ -174,9 +174,21 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
// 封装DTO逻辑不变
DeliveryDataDTO deliveryDataDTO = new DeliveryDataDTO();
deliveryDataDTO.setDeliveryHeader(listener.getDeliveryHeader());
deliveryDataDTO.setProductDetailList(listener.getProductDetailList());
deliveryDataDTO.setCustomerDeliveryDetailList(listener.getCustomerDeliveryDetailList());
List<ProductDetail> productDetailList = deliveryDataDTO.getProductDetailList();
// 判空处理货品明细和客户配送明细可能为空列表
List<ProductDetail> productDetailList = listener.getProductDetailList();
if (productDetailList == null) {
productDetailList = new ArrayList<>();
}
deliveryDataDTO.setProductDetailList(productDetailList);
List<CustomerDeliveryDetail> customerDeliveryDetailList = listener.getCustomerDeliveryDetailList();
if (customerDeliveryDetailList == null) {
customerDeliveryDetailList = new ArrayList<>();
}
deliveryDataDTO.setCustomerDeliveryDetailList(customerDeliveryDetailList);
LOGGER.info("Excel解析完成,货品明细{}条,客户配送明细{}条",
productDetailList.size(), customerDeliveryDetailList.size());
List<Map<String,Object>> shipperIds = new ArrayList<>();
Set<Long> processedShipperIds = new HashSet<>();
for (ProductDetail productDetail : productDetailList) {
@@ -192,20 +204,25 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
}
}
}
if (shipperIds.isEmpty()) {
throw new Exception("出库物料货主不存在!");
}
if (shipperIds.size() > 1) {
throw new Exception("出库物料不是同一个货主!");
// 货品明细不为空时校验货主信息
if (!productDetailList.isEmpty()) {
if (shipperIds.isEmpty()) {
throw new Exception("出库物料货主不存在!");
}
if (shipperIds.size() > 1) {
throw new Exception("出库物料不是同一个货主!");
}
}
ReservationStockOutOrderDO reservationStockOutOrderDO = new ReservationStockOutOrderDO();
reservationStockOutOrderDO.setOutOrderNumber(OrderSequence.getOrderCode("CK"));
reservationStockOutOrderDO.setShipperId((Long) shipperIds.get(0).get("shipperId"));
reservationStockOutOrderDO.setShipperName((String) shipperIds.get(0).get("shipperName"));
reservationStockOutOrderDO.setShipperCode((String) shipperIds.get(0).get("shipperCode"));
// 货主信息有货品明细时从解析结果取无货品明细时从登录用户取
if (!shipperIds.isEmpty()) {
reservationStockOutOrderDO.setShipperId((Long) shipperIds.get(0).get("shipperId"));
reservationStockOutOrderDO.setShipperName((String) shipperIds.get(0).get("shipperName"));
reservationStockOutOrderDO.setShipperCode((String) shipperIds.get(0).get("shipperCode"));
}
reservationStockOutOrderDO.setNeedTransportFlag("1");
reservationStockOutOrderDO.setNeedDeclareFlag("1");
reservationStockOutOrderDO.setOrganizationId(loginUser.getUserPo().getOrganizationId());
@@ -267,7 +284,7 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
reservationStockOutOrderDO.setMaterialDetailList(reservationOutMaterialDetailDOList);
reservationStockOutOrderApplicationService.insert(reservationStockOutOrderDO);
List<CustomerDeliveryDetail> customerDeliveryDetailList = deliveryDataDTO.getCustomerDeliveryDetailList();
// 客户配送明细判空处理为空时跳过关联保存
for (CustomerDeliveryDetail customerDeliveryDetail : customerDeliveryDetailList) {
ReservationDeliveryDetailsLink reservationDeliveryDetailsLink = new ReservationDeliveryDetailsLink();
reservationDeliveryDetailsLink.setInvoiceNumber(customerDeliveryDetail.getInvoiceNo());
@@ -388,7 +405,7 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
reservationInMaterialDetailDO.setCreateByName(loginUser.getUserPo().getUserName());
reservationInMaterialDetailDO.setCreateTime(new Date());
MaterialBaseInfoPO materialBaseInfoPO = stockInOrderMapper.getByCode(stockInProductDetail.getProductCode());
reservationInMaterialDetailDO.setQuantity(new BigDecimal(stockInProductDetail.getPieceCount()).setScale(2, BigDecimal.ROUND_HALF_UP));
reservationInMaterialDetailDO.setQuantity(new BigDecimal(stockInProductDetail.getBoxCount()).setScale(2, BigDecimal.ROUND_HALF_UP));
if (materialBaseInfoPO != null) {
reservationInMaterialDetailDO.setMaterialBaseInfoId(materialBaseInfoPO.getMaterialBaseInfoId());
reservationInMaterialDetailDO.setMaterialName(materialBaseInfoPO.getMaterialName());
@@ -295,43 +295,45 @@ public class ReservationStockOutOrderApplicationService {
{
ReservationStockOutOrderPO stockOutOrderPO = stockOutOrderDomainService.getInfoChildren(outOrderId);
String userNcCode = reservationStockInOrderMapper.getUserNcCode(stockOutOrderPO.getShipperId());
if (stockOutOrderPO == null || CollectionUtils.isEmpty(stockOutOrderPO.getMaterialDetailList())) {
if (stockOutOrderPO == null ) {
return stockOutOrderPO;
}
stockOutOrderPO.setKhdm(userNcCode);
List<ReservationOutMaterialDetailPO> materialDetailList = stockOutOrderPO.getMaterialDetailList();
if (CollectionUtils.isEmpty(materialDetailList)) {
for (ReservationOutMaterialDetailPO reservationOutMaterialDetailPO : materialDetailList) {
reservationOutMaterialDetailPO.setKhdm(userNcCode);
if (stockOutOrderPO.getMaterialDetailList() != null && stockOutOrderPO.getMaterialDetailList().size() > 0) {
List<ReservationOutMaterialDetailPO> materialDetailList = stockOutOrderPO.getMaterialDetailList();
if (CollectionUtils.isEmpty(materialDetailList)) {
for (ReservationOutMaterialDetailPO reservationOutMaterialDetailPO : materialDetailList) {
reservationOutMaterialDetailPO.setKhdm(userNcCode);
}
stockOutOrderPO.setMaterialDetailList(materialDetailList);
}
stockOutOrderPO.setMaterialDetailList(materialDetailList);
}
List<ReservationOutMaterialDetailPO> detailList = stockOutOrderPO.getMaterialDetailList();
String outOrderNumber = stockOutOrderPO.getOutOrderNumber();
for (ReservationOutMaterialDetailPO reservationOutMaterialDetailPO : detailList) {
Long materialBaseInfoId = reservationOutMaterialDetailPO.getMaterialBaseInfoId();
List<InventoryAdjustmentRecordPO> inventoryAdjustmentRecordPOS = reservationInMaterialDetailService.getkctzjl(materialBaseInfoId, outOrderNumber, "出库");
if (inventoryAdjustmentRecordPOS != null && inventoryAdjustmentRecordPOS.size() > 0) {
InventoryAdjustmentRecordPO inventoryAdjustmentRecordPO = inventoryAdjustmentRecordPOS.get(0);
reservationOutMaterialDetailPO.setAllocationBeforeQuantity(inventoryAdjustmentRecordPO.getAllocationBeforeQuantity());
reservationOutMaterialDetailPO.setAllocationAfterQuantity(inventoryAdjustmentRecordPO.getAllocationAfterQuantity());
reservationOutMaterialDetailPO.setFreezeAfterQuantity(inventoryAdjustmentRecordPO.getFreezeAfterQuantity());
reservationOutMaterialDetailPO.setFreezeBeforeQuantity(inventoryAdjustmentRecordPO.getFreezeBeforeQuantity());
reservationOutMaterialDetailPO.setInventoryAfterQuantity(inventoryAdjustmentRecordPO.getInventoryAfterQuantity());
reservationOutMaterialDetailPO.setInventoryBeforeQuantity(inventoryAdjustmentRecordPO.getInventoryBeforeQuantity());
reservationOutMaterialDetailPO.setNetWeight(inventoryAdjustmentRecordPO.getNetWeight());
reservationOutMaterialDetailPO.setGrossWeight(inventoryAdjustmentRecordPO.getGrossWeight());
reservationOutMaterialDetailPO.setVolume(inventoryAdjustmentRecordPO.getVolume());
reservationOutMaterialDetailPO.setArea(inventoryAdjustmentRecordPO.getArea());
reservationOutMaterialDetailPO.setAdjustedNetWeight(inventoryAdjustmentRecordPO.getAdjustedNetWeight());
reservationOutMaterialDetailPO.setAdjustedGrossWeight(inventoryAdjustmentRecordPO.getAdjustedGrossWeight());
reservationOutMaterialDetailPO.setAdjustedVolume(inventoryAdjustmentRecordPO.getAdjustedVolume());
reservationOutMaterialDetailPO.setAdjustedArea(inventoryAdjustmentRecordPO.getAdjustedArea());
}
if(materialBaseInfoId != null){
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(materialBaseInfoId);
if(materialBaseInfoPO != null && materialBaseInfoPO.getUnitPrice() != null){
reservationOutMaterialDetailPO.setUnitPrice(materialBaseInfoPO.getUnitPrice());
List<ReservationOutMaterialDetailPO> detailList = stockOutOrderPO.getMaterialDetailList();
String outOrderNumber = stockOutOrderPO.getOutOrderNumber();
for (ReservationOutMaterialDetailPO reservationOutMaterialDetailPO : detailList) {
Long materialBaseInfoId = reservationOutMaterialDetailPO.getMaterialBaseInfoId();
List<InventoryAdjustmentRecordPO> inventoryAdjustmentRecordPOS = reservationInMaterialDetailService.getkctzjl(materialBaseInfoId, outOrderNumber, "出库");
if (inventoryAdjustmentRecordPOS != null && inventoryAdjustmentRecordPOS.size() > 0) {
InventoryAdjustmentRecordPO inventoryAdjustmentRecordPO = inventoryAdjustmentRecordPOS.get(0);
reservationOutMaterialDetailPO.setAllocationBeforeQuantity(inventoryAdjustmentRecordPO.getAllocationBeforeQuantity());
reservationOutMaterialDetailPO.setAllocationAfterQuantity(inventoryAdjustmentRecordPO.getAllocationAfterQuantity());
reservationOutMaterialDetailPO.setFreezeAfterQuantity(inventoryAdjustmentRecordPO.getFreezeAfterQuantity());
reservationOutMaterialDetailPO.setFreezeBeforeQuantity(inventoryAdjustmentRecordPO.getFreezeBeforeQuantity());
reservationOutMaterialDetailPO.setInventoryAfterQuantity(inventoryAdjustmentRecordPO.getInventoryAfterQuantity());
reservationOutMaterialDetailPO.setInventoryBeforeQuantity(inventoryAdjustmentRecordPO.getInventoryBeforeQuantity());
reservationOutMaterialDetailPO.setNetWeight(inventoryAdjustmentRecordPO.getNetWeight());
reservationOutMaterialDetailPO.setGrossWeight(inventoryAdjustmentRecordPO.getGrossWeight());
reservationOutMaterialDetailPO.setVolume(inventoryAdjustmentRecordPO.getVolume());
reservationOutMaterialDetailPO.setArea(inventoryAdjustmentRecordPO.getArea());
reservationOutMaterialDetailPO.setAdjustedNetWeight(inventoryAdjustmentRecordPO.getAdjustedNetWeight());
reservationOutMaterialDetailPO.setAdjustedGrossWeight(inventoryAdjustmentRecordPO.getAdjustedGrossWeight());
reservationOutMaterialDetailPO.setAdjustedVolume(inventoryAdjustmentRecordPO.getAdjustedVolume());
reservationOutMaterialDetailPO.setAdjustedArea(inventoryAdjustmentRecordPO.getAdjustedArea());
}
if(materialBaseInfoId != null){
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(materialBaseInfoId);
if(materialBaseInfoPO != null && materialBaseInfoPO.getUnitPrice() != null){
reservationOutMaterialDetailPO.setUnitPrice(materialBaseInfoPO.getUnitPrice());
}
}
}
}
@@ -377,6 +379,10 @@ public class ReservationStockOutOrderApplicationService {
List<ReservationDeliveryDetailsLink> reservationDeliveryDetailsLinks = reservationDeliveryDetailsLinkMapper.selectList(new LambdaQueryWrapper<ReservationDeliveryDetailsLink>()
.eq(ReservationDeliveryDetailsLink::getOrderNumber, stockOutOrderPO.getOutOrderNumber()));
stockOutOrderPO.setReservationDeliveryDetailsLinkList(reservationDeliveryDetailsLinks);
Long shipperId = stockOutOrderPO.getShipperId();
if (shipperId != null && shipperId == 0L) {
stockOutOrderPO.setShipperId(null);
}
return stockOutOrderPO;
}
@@ -148,7 +148,11 @@ public class ReservationStockOutOrderDomainService {
reservationDeliveryDetailsLinkService.saveBatch(reservationDeliveryDetailsLinkList);
}
}
return outMaterialDetailService.batchInsert(stockOutOrderDO.getOutOrderNumber(), stockOutOrderDO.getMaterialDetailList());
List<ReservationOutMaterialDetailDO> materialDetailList = stockOutOrderDO.getMaterialDetailList();
if (materialDetailList != null && !materialDetailList.isEmpty()) {
return outMaterialDetailService.batchInsert(stockOutOrderDO.getOutOrderNumber(), stockOutOrderDO.getMaterialDetailList());
}
return true;
}
/**
@@ -979,7 +983,7 @@ public class ReservationStockOutOrderDomainService {
reservationStockInOrderMapper.insert(reservationStockInOrder);
reservationDeliveryDetailsLink.setReturnOrderNumber(reservationStockInOrder.getInOrderNumber());
reservationDeliveryDetailsLinkService.updateById(reservationDeliveryDetailsLink);
reservationDeliveryDetailsLinkService.updateW(reservationStockInOrder.getInOrderNumber(), reservationDeliveryDetailsLink.getOrderNumber());
reservationDeliveryDetailsLinkService.updateW(reservationStockInOrder.getInOrderNumber(), reservationDeliveryDetailsLink.getInvoiceNumber());
String returnOrderNumber = stockOutOrder.getReturnOrderNumber();
if (returnOrderNumber == null) {
stockOutOrder.setReturnOrderNumber(reservationStockInOrder.getInOrderNumber());
@@ -63,6 +63,10 @@ public class ReserveInMaterialDetail extends BaseVOEntity {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -63,6 +63,10 @@ public class ReserveInMaterialDetailPO extends ReserveStockInOrderBasePO {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -61,6 +61,10 @@ public class ReserveInMaterialDetailDO extends ReserveStockInOrderBaseDO {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -63,6 +63,10 @@ public class ReserveInMaterialDetailDTO extends BaseVOEntity {
@Excel(name = "物料名称")
private String materialName;
@ApiModelProperty("物料名称")
@Excel(name = "物料名称")
private String reserveMaterialName;
@ApiModelProperty("物料条形码")
@Excel(name = "物料条形码")
private String barCode;
@@ -670,7 +670,7 @@ public class ReserveStockInOrderDomainService {
MaterialBaseInfoPO materialBaseInfoPO = reservationStockInOrderMapper.getinfo(inMaterialDetailDO.getMaterialBaseInfoId());
//MaterialBaseInfoPO materialBaseInfoPO = materialBaseInfoService.getInfo(inMaterialDetailDO.getMaterialBaseInfoId());
if (materialBaseInfoPO == null){
throw new ServiceException("物料基础信息不存在");
return;
}
inMaterialDetailDO.setMaterialCode(materialBaseInfoPO.getMaterialCode());
inMaterialDetailDO.setMaterialName(materialBaseInfoPO.getMaterialName());
@@ -209,24 +209,28 @@ public class ReservationStockInOrderApi extends BaseController {
/**
* 上传并解析Excel文件
* 上传并解析Excel文件默认读取"出库导入"工作表
* @param file Excel文件
* @param sheetName 工作表名称出库导入/参考数据
* @return 解析后的出库数据
*/
@PostMapping("/parseExcel")
public ResponseEntity<Map<String, Object>> parseExcel(
@RequestParam("file") MultipartFile file) {
Map<String, Object> result = new HashMap<>();
try {
DeliveryDataDTO deliveryDataDTO = reservationStockInOrderService.parseExcelFile(file, "");
Map<String, Object> result = new HashMap<>();
result.put("result", deliveryDataDTO);
result.put("code",200);
result.put("code", 200);
result.put("msg", "解析成功");
return new ResponseEntity<>(result, HttpStatus.OK);
} catch (IllegalArgumentException e) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
result.put("code", 400);
result.put("msg", e.getMessage());
return new ResponseEntity<>(result, HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<>(HttpStatus.INTERNAL_SERVER_ERROR);
result.put("code", 500);
result.put("msg", e.getMessage());
return new ResponseEntity<>(result, HttpStatus.INTERNAL_SERVER_ERROR);
}
}
@@ -61,6 +61,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
</select>
<update id="updateW">
update NGWL_TEST_WMS.DELIVERY_DETAILS_LINK set RETURN_ORDER_NUMBER = #{returnOrderNumber} where ORDER_NUMBER = #{orderNumber}
update NGWL_TEST_WMS.DELIVERY_DETAILS_LINK set RETURN_ORDER_NUMBER = #{returnOrderNumber} where INVOICE_NUMBER = #{orderNumber}
</update>
</mapper>
@@ -96,6 +96,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<result property="reservePrice" column="reserve_price" />
<result property="reserveUnitPrice" column="reserve_unit_price" />
<result property="shelfLife" column="shelf_life" />
<result property="reserveMaterialName" column="RESERVE_MATERIAL_NAME" />
</resultMap>
@@ -109,7 +110,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
a.lot_no ,a.reserve_quantity,a.reserve_gross_weight,a.reserve_net_weight,a.reserve_volume,a.reserve_area,a.reserve_price,a.reserve_unit_price,a.shelf_life,
a.material_detail_id, a.organization_id, a.organization_name, a.top_organization_id, a.unique_id, a.in_order_number, a.material_base_info_id,
a.material_code, a.material_name, a.bar_code, a.quantity, a.receipt_quantity, a.shelves_quantity,
a.receipt_status, a.shelves_status, a.pack_id, a.pack_code, a.pack_name,
a.receipt_status, a.shelves_status, a.pack_id, a.pack_code, a.pack_name,a.RESERVE_MATERIAL_NAME
CASE
WHEN a.pack_detail_id IS NULL OR TRIM(a.pack_detail_id) = '' THEN NULL
ELSE CAST(a.pack_detail_id AS BIGINT)