feat(oms):出库导入校验送貨線號/車次并回填线路名称

1. 出库导入(预约/执行两侧)增加validateLineAndShuttle校验:
   送貨線號/車次缺失直接报错;线路须在WMS线路管理存在(route_code精确匹配);
   车次须在该线路车次清单(shuttle_json)内;校验通过回填lineName,
   线路/车次一并落lineCode/lineName/shuttleNo。
2. WmsServiceFeign新增listLineManagement(复用WMS /lineManagementApi/listByApp)
   供OMS跨库查线路,附降级实现;顺带移除FeignClient写死的本机url,恢复Nacos服务发现。
3. 执行侧DynamicDeliveryExcelListener(关键字版)补送貨線號/車次独立成行的解析,
   原实现仅支持与送貨日期同行取值,该模板为分行布局导致解析不到。
This commit is contained in:
rcx
2026-08-22 14:08:22 +08:00
parent ab7219e600
commit a08d8a7fd5
5 changed files with 164 additions and 0 deletions
@@ -151,4 +151,12 @@ public interface WmsServiceFeign {
@ApiOperation("OMS批量签名-同步WMS入/出库单签名字段")
@PostMapping("/stockSignatureApi/batchSign")
public AjaxResult batchSignWms(@RequestBody SignatureBatchFeignDTO dto);
/**
* OMS查询WMS线路管理列表(按线路编码模糊查,OMS侧自行精确匹配),
* 出库导入时校验送貨線號/車次并回填线路名称
*/
@ApiOperation("OMS查询WMS线路管理列表")
@GetMapping("/lineManagementApi/listByApp")
public AjaxResult listLineManagement(@RequestParam("routeCode") String routeCode);
}
@@ -125,6 +125,12 @@ public class RemoteWmsFallbackFactory implements FallbackFactory<WmsServiceFeign
log.error("WMS批量签名调用失败");
return AjaxResult.error("WMS批量签名调用失败:" + cause.getMessage());
}
@Override
public AjaxResult listLineManagement(String routeCode) {
log.error("OMS查询WMS线路管理失败, routeCode:{}", routeCode);
return AjaxResult.error("WMS线路管理查询失败:" + cause.getMessage());
}
};
}
}
@@ -34,6 +34,8 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
private static final Pattern DECIMAL_PATTERN = Pattern.compile("-?\\d+(\\.\\d+)?");
// 关键字定义(Excel标签匹配)
private static final String KEYWORD_DELIVERY_DATE = "送貨日期";
private static final String KEYWORD_DELIVERY_LINE_NO = "送貨線號";
private static final String KEYWORD_TRAIN_NUMBER = "車次";
private static final String KEYWORD_PRODUCT_DETAIL = "货品明细";
private static final String KEYWORD_TOTAL_RECORD = "紀錄合共";
private static final String KEYWORD_CUSTOMER_DETAIL = "客户配送明细";
@@ -89,6 +91,20 @@ public class DynamicDeliveryExcelListener extends AnalysisEventListener<Map<Stri
// 空闲状态:解析表头信息(送货日期行,多单元格数据)
if (firstCellValue.contains(KEYWORD_DELIVERY_DATE)) {
parseHeaderInfo(data, rowNum);
} else if (firstCellValue.contains(KEYWORD_DELIVERY_LINE_NO)) {
// 送货线号独立成行(A列=标签,B列=值),覆盖parseHeaderInfo按同行取值的兜底逻辑
String deliveryLineNo = getValueSafely(data, 1, String.class);
if (deliveryLineNo != null && !deliveryLineNo.trim().isEmpty()) {
deliveryHeader.setDeliveryLineNo(deliveryLineNo.trim());
}
LOGGER.debug("第{}行:解析送货线号={}", rowNum, deliveryLineNo);
} else if (firstCellValue.contains(KEYWORD_TRAIN_NUMBER)) {
// 车次独立成行(A列=标签,B列=值)
Integer trainNumber = getValueSafely(data, 1, Integer.class);
if (trainNumber != null) {
deliveryHeader.setTrainNumber(trainNumber);
}
LOGGER.debug("第{}行:解析车次={}", rowNum, trainNumber);
}
break;
}
@@ -1,6 +1,7 @@
package com.mhd.oms.domain.executionStockInOrder.repository.persistence;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
@@ -36,6 +37,7 @@ import com.mhd.oms.domain.reservationDeliveryDetailsLink.entity.ReservationDeliv
import com.mhd.oms.domain.reservationDeliveryDetailsLink.repository.facade.IReservationDeliveryDetailsLinkService;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.UserServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.SysDictData;
import com.mhd.system.api.domain.WarehouseFeignPO;
import com.mhd.system.api.domain.cache.AssociationWarehouseCacheDO;
@@ -67,6 +69,8 @@ public class ExecutionStockInOrderImpl extends ServiceImpl<ExecutionStockInOrder
@Autowired
private ExecutionStockOutOrderMapper stockOutOrderMapper;
@Autowired
private WmsServiceFeign wmsServiceFeign;
@Autowired
private IReservationDeliveryDetailsLinkService reservationDeliveryDetailsLinkService;
@Autowired
private ExecutionStockOutOrderApplicationService reservationStockOutOrderApplicationService;
@@ -144,6 +148,64 @@ public class ExecutionStockInOrderImpl extends ServiceImpl<ExecutionStockInOrder
return stockInOrderMapper.queryNoticePrintDetail(stockInOrderDO);
}
/**
* 出库导入-送貨線號(线路)/車次校验
* 1.送貨線號/車次缺失直接报错
* 2.线路须存在于WMS线路管理(line_management,按route_code精确匹配)
* 3.车次须在该线路维护的车次清单(shuttle_json)内
* 校验通过返回线路名称,用于回填lineName
*/
private String validateLineAndShuttle(DeliveryHeader deliveryHeader) throws Exception {
String lineCode = deliveryHeader.getDeliveryLineNo();
Integer trainNumber = deliveryHeader.getTrainNumber();
if (StringUtils.isBlank(lineCode)) {
throw new Exception("Excel未识别到【送貨線號】(线路),请检查模板第2行后重新导入");
}
if (trainNumber == null || trainNumber <= 0) {
throw new Exception("Excel未识别到【車次】,请检查模板第3行后重新导入");
}
AjaxResult lineResult = wmsServiceFeign.listLineManagement(lineCode);
if (lineResult == null || lineResult.get(AjaxResult.DATA_TAG) == null) {
throw new Exception("查询WMS线路管理失败,请稍后重试");
}
String lineName = null;
String shuttleJson = null;
boolean lineFound = false;
List<Map<String, Object>> lineList = (List<Map<String, Object>>) lineResult.get(AjaxResult.DATA_TAG);
for (Map<String, Object> line : lineList) {
//listByApp按routeCode模糊查,此处做精确匹配
if (lineCode.equals(String.valueOf(line.get("routeCode")))) {
lineFound = true;
Object routeName = line.get("routeName");
lineName = routeName == null ? null : String.valueOf(routeName);
Object shuttleObj = line.get("shuttleJson");
shuttleJson = shuttleObj == null ? null : String.valueOf(shuttleObj);
break;
}
}
if (!lineFound) {
throw new Exception("线路【" + lineCode + "】在WMS线路管理中不存在,请先在基础数据-线路管理中维护");
}
boolean shuttleMatched = false;
if (StringUtils.isNotBlank(shuttleJson)) {
try {
JSONArray shuttleArray = JSONUtil.parseArray(shuttleJson);
for (Object shuttle : shuttleArray) {
if (String.valueOf(trainNumber).equals(String.valueOf(shuttle))) {
shuttleMatched = true;
break;
}
}
} catch (Exception e) {
shuttleMatched = false;
}
}
if (!shuttleMatched) {
throw new Exception("线路【" + lineCode + "】维护的车次清单(" + shuttleJson + ")中不存在车次【" + trainNumber + "】,请先在WMS线路管理中维护该车次");
}
return StringUtils.isNotBlank(lineName) ? lineName : lineCode;
}
@Override
public DeliveryDataDTO parseExcelFile(MultipartFile file, String sheetName) throws Exception {
LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -223,6 +285,11 @@ public class ExecutionStockInOrderImpl extends ServiceImpl<ExecutionStockInOrder
reservationStockOutOrderDO.setReservationOrderSource("数据导入");
reservationStockOutOrderDO.setOutboundDate(deliveryHeader.getDeliveryDate());
reservationStockOutOrderDO.setTrainNo(deliveryHeader.getTrainNumber());
//送貨線號(线路)/車次校验:必填、线路须在WMS线路管理存在、车次须在线路车次清单内;通过后回填线路名称
String lineName = validateLineAndShuttle(deliveryHeader);
reservationStockOutOrderDO.setLineCode(deliveryHeader.getDeliveryLineNo());
reservationStockOutOrderDO.setLineName(lineName);
reservationStockOutOrderDO.setShuttleNo(String.valueOf(deliveryHeader.getTrainNumber()));
int index = 1;
List<ExecutionOutMaterialDetailDO> reservationOutMaterialDetailDOList = new ArrayList<>();
for (ProductDetail productDetail : productDetailList) {
@@ -1,6 +1,7 @@
package com.mhd.oms.domain.reservationStockInOrder.repository.persistence;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.EasyExcel;
import com.alibaba.fastjson.JSON;
@@ -42,6 +43,7 @@ import com.mhd.oms.domain.reservationStockOutOrder.service.ReservationStockOutOr
import com.mhd.oms.domain.reserveStockInOrder.repository.mapper.ReserveStockInOrderMapper;
import com.mhd.system.api.SystemServiceFeign;
import com.mhd.system.api.UserServiceFeign;
import com.mhd.system.api.WmsServiceFeign;
import com.mhd.system.api.domain.SysDictData;
import com.mhd.system.api.domain.WarehouseFeignPO;
import com.mhd.system.api.domain.cache.AssociationWarehouseCacheDO;
@@ -73,6 +75,8 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
@Autowired
private ReservationStockOutOrderMapper stockOutOrderMapper;
@Autowired
private WmsServiceFeign wmsServiceFeign;
@Autowired
private IReservationDeliveryDetailsLinkService reservationDeliveryDetailsLinkService;
@Autowired
private IReservationStockOutOrderService reservationStockOutOrderService;
@@ -157,6 +161,64 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
return stockInOrderMapper.queryNoticePrintDetail(stockInOrderDO);
}
/**
* 出库导入-送貨線號(线路)/車次校验
* 1.送貨線號/車次缺失直接报错
* 2.线路须存在于WMS线路管理(line_management,按route_code精确匹配)
* 3.车次须在该线路维护的车次清单(shuttle_json)内
* 校验通过返回线路名称,用于回填lineName
*/
private String validateLineAndShuttle(DeliveryHeader deliveryHeader) throws Exception {
String lineCode = deliveryHeader.getDeliveryLineNo();
Integer trainNumber = deliveryHeader.getTrainNumber();
if (StringUtils.isBlank(lineCode)) {
throw new Exception("Excel未识别到【送貨線號】(线路),请检查模板第2行后重新导入");
}
if (trainNumber == null || trainNumber <= 0) {
throw new Exception("Excel未识别到【車次】,请检查模板第3行后重新导入");
}
AjaxResult lineResult = wmsServiceFeign.listLineManagement(lineCode);
if (lineResult == null || lineResult.get(AjaxResult.DATA_TAG) == null) {
throw new Exception("查询WMS线路管理失败,请稍后重试");
}
String lineName = null;
String shuttleJson = null;
boolean lineFound = false;
List<Map<String, Object>> lineList = (List<Map<String, Object>>) lineResult.get(AjaxResult.DATA_TAG);
for (Map<String, Object> line : lineList) {
//listByApp按routeCode模糊查,此处做精确匹配
if (lineCode.equals(String.valueOf(line.get("routeCode")))) {
lineFound = true;
Object routeName = line.get("routeName");
lineName = routeName == null ? null : String.valueOf(routeName);
Object shuttleObj = line.get("shuttleJson");
shuttleJson = shuttleObj == null ? null : String.valueOf(shuttleObj);
break;
}
}
if (!lineFound) {
throw new Exception("线路【" + lineCode + "】在WMS线路管理中不存在,请先在基础数据-线路管理中维护");
}
boolean shuttleMatched = false;
if (StringUtils.isNotBlank(shuttleJson)) {
try {
JSONArray shuttleArray = JSONUtil.parseArray(shuttleJson);
for (Object shuttle : shuttleArray) {
if (String.valueOf(trainNumber).equals(String.valueOf(shuttle))) {
shuttleMatched = true;
break;
}
}
} catch (Exception e) {
shuttleMatched = false;
}
}
if (!shuttleMatched) {
throw new Exception("线路【" + lineCode + "】维护的车次清单(" + shuttleJson + ")中不存在车次【" + trainNumber + "】,请先在WMS线路管理中维护该车次");
}
return StringUtils.isNotBlank(lineName) ? lineName : lineCode;
}
@Override
public DeliveryDataDTO parseExcelFile(MultipartFile file, String sheetName) throws Exception {
LoginUser loginUser = SecurityUtils.getLoginUser();
@@ -258,6 +320,11 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
reservationStockOutOrderDO.setReservationOrderSource("数据导入");
reservationStockOutOrderDO.setOutboundDate(deliveryHeader.getDeliveryDate());
reservationStockOutOrderDO.setTrainNo(deliveryHeader.getTrainNumber());
//送貨線號(线路)/車次校验:必填、线路须在WMS线路管理存在、车次须在线路车次清单内;通过后回填线路名称
String lineName = validateLineAndShuttle(deliveryHeader);
reservationStockOutOrderDO.setLineCode(deliveryHeader.getDeliveryLineNo());
reservationStockOutOrderDO.setLineName(lineName);
reservationStockOutOrderDO.setShuttleNo(String.valueOf(deliveryHeader.getTrainNumber()));
int index = 1;
List<ReservationOutMaterialDetailDO> reservationOutMaterialDetailDOList = new ArrayList<>();
for (ProductDetail productDetail : productDetailList) {