feat(oms):入库模板导入支持模板自带业务单号,重复拦截并按R+年月日规则生成

1. 模板OrderNO解析兼容无标签新模板(E1直接为单号值)
2. 导入单号优先模板自带:先校验R+年月日+3位序号格式(含年月日有效性),
   再跨reservation/reserve两表查重,重复提示"入库业务单号已存在"
3. 模板无单号时按R+年月日+当日累加生成(insertDomain不再无条件覆盖已设置单号,清理无效的仓库规则生成死代码)
This commit is contained in:
rcx
2026-08-25 16:11:19 +08:00
parent 735cf9af9f
commit 7353021d92
2 changed files with 70 additions and 71 deletions
@@ -92,18 +92,36 @@ public class StockInExcelListener extends AnalysisEventListener<Map<String, Obje
LOGGER.debug("第1行:入库日期解析完成,值:{}", stockInDate);
}
// OrderNO:查找包含"OrderNO"标签的下一个值
// OrderNO优先查找"OrderNO"标签后的值(老模板布局,标签与值相邻);
// 新模板第1行无"OrderNO"标签,单号值直接放在单元格中(如E1),
// 此时取第1行中除标签、入库日期以外的第一个非空字符串值
Object orderNoObj = getValueAfterLabel(data, "OrderNO");
if (orderNoObj == null) {
for (Object value : data.values()) {
if (value == null) {
continue;
}
String str = value.toString().trim();
// 跳过标签单元格与已被识别为入库日期的值
if (str.isEmpty() || str.contains("入庫日期") || str.contains("OrderNO") || str.contains("Total")) {
continue;
}
if (value == stockInDateObj) {
continue;
}
orderNoObj = value;
break;
}
}
if (orderNoObj != null) {
String orderNo = orderNoObj.toString().trim();
stockInHeader.setOrderNo(orderNo);
LOGGER.debug("第1行:OrderNO解析完成,值:{}", orderNo);
}
// 校验表头数据完整性
if (stockInHeader.getStockInDate() == null || stockInHeader.getOrderNo() == null || stockInHeader.getOrderNo().isEmpty()) {
LOGGER.warn("第1行表头数据不完整,入库日期:{}OrderNO{}",
stockInHeader.getStockInDate(), stockInHeader.getOrderNo());
// 校验表头数据完整性(OrderNO允许为空:为空时由系统按规则生成业务单号)
if (stockInHeader.getStockInDate() == null) {
LOGGER.warn("第1行表头数据不完整,入库日期:{}", stockInHeader.getStockInDate());
}
}
@@ -485,10 +485,26 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
throw new ServiceException("导入的产品均未配置货主,请先在物料档案中维护货主信息");
}
ReservationStockInOrderDO reservationStockInOrderDO = new ReservationStockInOrderDO();
// 生成业务单号
String orderNumber = reservationStockInOrderDomainService.generateReserveOrderNumber();
reservationStockInOrderDO.setInOrderNumber(orderNumber);
reservationStockInOrderDO.setBusinessOrderNo(orderNumber);
StockInHeader stockInHeader = resultDTO.getStockInHeader();
// 业务单号:优先使用模板自带OrderNO,与系统已有入库业务单号重复时提示;
// 模板未填单号时按 R+年月日+当日累加 规则生成(如R260717001、R260717002
String templateOrderNo = stockInHeader.getOrderNo() == null ? "" : stockInHeader.getOrderNo().trim();
if (!templateOrderNo.isEmpty()) {
// 先校验模板单号是否符合规则:R+年月日+3位及以上序号,如R260717001
validateTemplateOrderNo(templateOrderNo);
// 跨两张入库单表查重(与手工新增取号逻辑保持一致)
// 注意:本类字段命名易混淆——stockInOrderMapper为reservation表,reservationStockInOrderMapper为reserve表
boolean duplicated = stockInOrderMapper.existsByOrderNumber(templateOrderNo) > 0
|| reservationStockInOrderMapper.countByInOrderNumber(templateOrderNo) > 0;
if (duplicated) {
throw new ServiceException("入库业务单号已存在:" + templateOrderNo + ",请修改模板中的OrderNO后重新导入");
}
}
if (templateOrderNo.isEmpty()) {
templateOrderNo = reservationStockInOrderDomainService.generateReserveOrderNumber();
}
reservationStockInOrderDO.setInOrderNumber(templateOrderNo);
reservationStockInOrderDO.setBusinessOrderNo(templateOrderNo);
reservationStockInOrderDO.setOrganizationId(loginUser.getUserPo().getOrganizationId());
reservationStockInOrderDO.setOrganizationName(loginUser.getUserPo().getOrganizationName());
reservationStockInOrderDO.setTopOrganizationId(loginUser.getUserPo().getTopOrganizationId());
@@ -510,7 +526,6 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
reservationStockInOrderDO.setShipperId((Long) shipperIds.get(0).get("shipperId"));
reservationStockInOrderDO.setShipperName((String) shipperIds.get(0).get("shipperName"));
reservationStockInOrderDO.setShipperCode((String) shipperIds.get(0).get("shipperCode"));
StockInHeader stockInHeader = resultDTO.getStockInHeader();
reservationStockInOrderDO.setWarehouseDate(stockInHeader.getStockInDate());
reservationStockInOrderDO.setStorageTime(stockInHeader.getStockInDate());
reservationStockInOrderDO.setExpectTime(stockInHeader.getStockInDate());
@@ -583,75 +598,41 @@ public class ReservationStockInOrderImpl extends ServiceImpl<ReservationStockInO
/**
* 从单号列表中提取最大序号
* 单号格式: prefix + 数字序号(如 RK260807001 中 prefix=RK260807, 序号=001
* 即使中间有删单,也能保证序号递增不重复
*
* @param orderNumbers 匹配的单号列表
* @param prefixLength 前缀长度(序号从该位置开始截取)
* @return 最大序号值,列表为空时返回0
* 校验模板自带业务单号是否符合规则:R + 年月日(yyMMdd) + 3位及以上序号
* 示例:R260717001、R260717002
* 格式不符或年月日部分非有效日期(如R261332001)时抛出提示
*/
private int extractMaxSequenceNumber(List<String> orderNumbers, int prefixLength) {
if (orderNumbers == null || orderNumbers.isEmpty()) {
return 0;
private void validateTemplateOrderNo(String orderNo) {
// R + 6位年月日 + 至少3位数字序号
if (!orderNo.matches("R\\d{9,}")) {
throw new ServiceException("入库业务单号格式不正确:" + orderNo
+ ",请按照[代码R+年月日+当日累加]规则填写单号,正确格式为R+年月日+3位序号,如R260717001");
}
int maxSeq = 0;
for (String orderNumber : orderNumbers) {
if (orderNumber != null && orderNumber.length() > prefixLength) {
try {
String seqStr = orderNumber.substring(prefixLength);
int seq = Integer.parseInt(seqStr);
if (seq > maxSeq) {
maxSeq = seq;
}
} catch (NumberFormatException e) {
// 序号部分非数字,跳过
}
}
try {
SimpleDateFormat fmt = new SimpleDateFormat("yyMMdd");
fmt.setLenient(false);
fmt.parse(orderNo.substring(1, 7));
} catch (java.text.ParseException e) {
throw new ServiceException("入库业务单号格式不正确:" + orderNo
+ ",请按照[代码R+年月日+当日累加]规则填写单号,单号中的年月日无效,正确格式为R+年月日+3位序号,如R260717001");
}
return maxSeq;
}
@Transactional(rollbackFor = Exception.class)
public Boolean insertDomain(ReservationStockInOrderDO stockInOrderDO) {
// if (stockInOrderDO.getInOrderNumber() == null) {
//设置入库单号
Long warehouseId = stockInOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
//仓库信息
WarehouseFeignPO warehouseFeignPO = com.alibaba.fastjson.JSON.parseObject(com.alibaba.fastjson2.JSONObject.toJSONString(ajaxResult.get("data")), WarehouseFeignPO.class);
String orderNoGenerateRule = warehouseFeignPO.getOrderNoGenerateRule();
String prefix = warehouseFeignPO.getInOrderNoPrefix();
if (prefix == null || prefix.isEmpty()) {
prefix = "RK";
}
Long organizationId = stockInOrderDO.getOrganizationId();
Long warehouseIdForCount = stockInOrderDO.getWarehouseId();
if (orderNoGenerateRule != null && "0".equals(orderNoGenerateRule)) {
//日期累加: 前缀 + 当前日期yymmdd + 当天最大序号+1
String dateStr = new SimpleDateFormat("yyMMdd").format(new Date());
String datePrefix = prefix + dateStr;
List<String> orderNumbers = reservationStockInOrderMapper.getMaxStockInOrderByPrefixAndOrgAndWarehouse(datePrefix, organizationId, warehouseIdForCount);
int maxSeq = extractMaxSequenceNumber(orderNumbers, datePrefix.length());
String seq = String.format("%03d", maxSeq + 1);
stockInOrderDO.setInOrderNumber(datePrefix + seq);
stockInOrderDO.setBusinessOrderNo(datePrefix + seq);
} else {
//历史累加: 前缀 + 历史最大序号+1
List<String> orderNumbers = reservationStockInOrderMapper.getMaxStockInOrderByPrefixAndOrgAndWarehouse(prefix, organizationId, warehouseIdForCount);
int maxSeq = extractMaxSequenceNumber(orderNumbers, prefix.length());
String seq = String.format("%03d", maxSeq + 1);
stockInOrderDO.setInOrderNumber(prefix + seq);
stockInOrderDO.setBusinessOrderNo(prefix + seq);
}
// 仓库信息校验(保留原有行为:仓库信息获取失败直接报错)
Long warehouseId = stockInOrderDO.getWarehouseId();
AjaxResult ajaxResult = systemServiceFeign.getWarehouseInfoByWarehouseId(warehouseId);
if (!"200".equals(String.valueOf(ajaxResult.get("code")))) {
throw new ServiceException("获取仓库信息失败");
}
// 入库单号:调用方已设置(如导入时优先使用模板自带OrderNO,且已校验不重复)则沿用,不再覆盖;
// 未设置时按 R+年月日+当日累加 规则生成(如R260717001、R260717002),跨reservation/reserve两表取号防撞
if (stockInOrderDO.getInOrderNumber() == null || stockInOrderDO.getInOrderNumber().trim().isEmpty()) {
stockInOrderDO.setInOrderNumber(reservationStockInOrderDomainService.generateReserveOrderNumber());
stockInOrderDO.setBusinessOrderNo(stockInOrderDO.getInOrderNumber());
// }
// //统计总计划数
}
stockInOrderDO.setBusinessOrderNo(stockInOrderDO.getInOrderNumber());
//统计总计划数
BigDecimal quantity = stockInOrderDO.getMaterialDetailList().stream().map(ReservationInMaterialDetailDO::getQuantity).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
stockInOrderDO.setQuantity(quantity);
BigDecimal amount = stockInOrderDO.getMaterialDetailList().stream().map(ReservationInMaterialDetailDO::getTotalPrice).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);