Browse Source

feat(inventory): 实现盘点单WCS推送的差异化策略

- 区分循环盘点和手工盘点的不同推送策略
- 循环盘点:每次推送10个栈板,最多1个任务并发
- 手工盘点:根据总栈板数决定推送策略,最多2个任务并发
- 完善自动流转机制,支持"推送→盘点→推送→盘点"循环
- 增强任务完成后的自动推送下一批功能
- 添加详细的业务逻辑注释和并发控制说明
master
常熟吴彦祖 3 weeks ago
parent
commit
745a5d829a
  1. 276
      src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java

276
src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java

@ -756,16 +756,41 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
}
/**
* @Description 推送盘点单到WCS首次推送推前10个栈板- rqrq
* @Description 推送盘点单到WCS首次推送- rqrq
*
* <p><b>业务逻辑</b></p>
* <p><b>业务逻辑重要请仔细阅读</b></p>
* <pre>
* 循环盘点CYCLE
* 1. 首次推送1个任务包含10个栈板
* 2. 任务完成后自动推送下一批10个栈板
* 3. 始终保持最多1个任务在流转
*
* 手工盘点MANUAL
* 1. 首次推送策略根据总栈板数决定
* - 总栈板数 5推送1个任务包含所有栈板
* - 总栈板数 > 5推送2个任务每个任务最多5个栈板
* 例如8个栈板 任务15个+ 任务23个
* 15个栈板 任务15个+ 任务25个剩余5个等后续推送
* 2. 因为手工盘点有2个通道可以2个人同时操作
* 3. 每个任务完成后自动推送下一批5个栈板
* 4. 始终保持最多2个任务同时流转
*
* 推送流程
* 1. 校验盘点单状态必须为"已下达"(RELEASED)
* 2. 生成wms_order_task单据明细是各个栈板
* 3. 每次推送10个栈板尽量每层都有栈板按层轮询选取
* 4. 推送成功后状态改为"盘点中"(CHECKING)
* 5. 第10个栈板出库后由其他方法调用continuePushCount推送后续栈板
* 2. 根据盘点类型执行不同的推送策略
* 3. 生成wms_order_task单据明细是各个栈板
* 4. 推送给WCS由立库系统执行出库
* 5. 推送成功后更新盘点单状态为"盘点中"(CHECKING)
*
* 自动流转机制
* - 当任务的所有托盘盘点完成后会自动调用continuePushCount推送下一批
* - 形成"推送→盘点→推送→盘点"的自动循环
* - 直到所有托盘都推送完成
* </pre>
*
* @param query 推送参数包含sitecountNousername
* @return int 本次推送的栈板总数
* @author rqrq
*/
@Override
@Transactional(rollbackFor = Exception.class)
@ -781,11 +806,61 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
throw new RuntimeException("只有已下达状态的盘点单才能推送");
}
// 2. 执行推送 - rqrq
int pushedCount = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 10);
// 2. 根据盘点类型确定推送策略 - rqrq
boolean isManual = CountHeader.COUNT_TYPE_MANUAL.equals(header.getCountType());
int totalPushedCount = 0;
if (isManual) {
// ========== 手工盘点首次推送策略 ========== - rqrq
log.info("手工盘点,执行手工盘点推送策略");
// 2.1 查询未推送的托盘总数 - rqrq
List<CountPalletData> unpushedPallets = baseMapper.queryUnpushedPallets(query.getSite(), query.getCountNo());
int totalUnpushedCount = unpushedPallets != null ? unpushedPallets.size() : 0;
log.info("未推送托盘总数: {}", totalUnpushedCount);
if (totalUnpushedCount == 0) {
log.warn("没有未推送的托盘,跳过推送");
return 0;
}
// 2.2 根据托盘总数决定推送策略 - rqrq
if (totalUnpushedCount <= 5) {
// 托盘数5推送1个任务包含所有托盘- rqrq
log.info("托盘数≤5,推送1个任务(包含所有托盘)");
int pushedCount = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 5);
totalPushedCount += pushedCount;
log.info("推送完成,推送数量: {}", pushedCount);
} else {
// 托盘数>5推送2个任务每个最多5个托盘- rqrq
log.info("托盘数>5,推送2个任务(每个最多5个托盘)");
// 推送第1个任务5个托盘- rqrq
log.info("开始推送第1个任务");
int pushedCount1 = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 5);
totalPushedCount += pushedCount1;
log.info("第1个任务推送完成,推送数量: {}", pushedCount1);
// 推送第2个任务最多5个托盘- rqrq
// 只有第1个任务推送成功才推送第2个避免第1个失败后第2个还推送- rqrq
if (pushedCount1 > 0) {
log.info("开始推送第2个任务");
int pushedCount2 = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 5);
totalPushedCount += pushedCount2;
log.info("第2个任务推送完成,推送数量: {}", pushedCount2);
} else {
log.warn("第1个任务推送数量为0,跳过第2个任务推送");
}
}
} else {
// ========== 循环盘点推送1个任务10个托盘========== - rqrq
log.info("循环盘点,推送1个任务(10个托盘)");
totalPushedCount = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 10);
log.info("推送完成,推送数量: {}", totalPushedCount);
}
// 3. 更新盘点单状态为"盘点中" - rqrq
if (pushedCount > 0) {
if (totalPushedCount > 0) {
CountHeader updateHeader = new CountHeader();
updateHeader.setSite(query.getSite());
updateHeader.setCountNo(query.getCountNo());
@ -793,23 +868,70 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
updateHeader.setModifiedBy(query.getUsername());
baseMapper.updateCountHeaderStatus(updateHeader);
log.info("盘点单状态已更新为CHECKING");
} else {
log.warn("推送数量为0,未更新盘点单状态");
}
log.info("pushCountToWcs 结束,本次推送栈板数: {}", pushedCount);
return pushedCount;
log.info("pushCountToWcs 结束,本次推送栈板数: {}", totalPushedCount);
return totalPushedCount;
}
/**
* @Description 继续推送盘点单到WCS推后续10个栈板由其他交互调用- rqrq
* @Description 继续推送盘点单到WCS自动推送下一批由任务完成后自动触发- rqrq
*
* <p><b>业务逻辑</b></p>
* <p><b>业务逻辑重要请仔细阅读</b></p>
* <pre>
* 调用时机
* - 由handleTaskAfterCount方法在任务完成后自动调用
* - 不是手动调用而是自动触发的
*
* 循环盘点CYCLE
* 1. 校验未完成任务数最多允许1个任务
* 2. 有未完成任务时直接返回0不推送
* 3. 无未完成任务时推送下一批10个栈板
*
* 手工盘点MANUAL
* 1. 校验未完成任务数最多允许2个任务
* 2. 已有2个未完成任务时直接返回0不推送等待其他任务完成
* 3. 未完成任务数<2时推送下一批5个栈板
* 4. 形成动态平衡始终保持最多2个任务同时流转
*
* 流转示例 - 手工盘点15个托盘
* 首次推送任务15个+ 任务25个
*
* 任务1完成 continuePushCount触发
* 检查未完成任务数1个任务2
* 判断1 < 2允许推送
* 推送任务35个
* 现在任务2 + 任务3 并行
*
* 任务2完成 continuePushCount触发
* 检查未完成任务数1个任务3
* 判断1 < 2允许推送
* 查询未推送托盘数0个
* 返回0无更多托盘
*
* 任务3完成 continuePushCount触发
* 查询未推送托盘数0个
* 返回0无更多托盘
*
* 并发控制机制
* - 通过查询未完成任务数来控制并发
* - 循环盘点最多1个任务单通道
* - 手工盘点最多2个任务双通道
* - 超过上限时返回0不抛异常因为是自动触发不需要中断流程
*
* 推送流程
* 1. 校验盘点单状态必须是"盘点中"
* 2. 校验是否存在未完成的任务单防止重复下达
* 3. 查询未推送的栈板task_no为空的
* 4. 每次推送10个栈板尽量每层都有栈板
* 2. 查询当前未完成的任务数
* 3. 判断是否达到并发上限
* 4. 未达到上限则推送下一批栈板
* 5. 生成wms_order_task单据并推送给WCS
* </pre>
*
* @param query 推送参数包含sitecountNousername
* @return int 本次推送的栈板数0表示未推送或无更多托盘
* @author rqrq
*/
@Override
@Transactional(rollbackFor = Exception.class)
@ -825,17 +947,41 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
throw new RuntimeException("盘点单状态不是盘点中,终止盘点");
}
// 2. 校验是否存在未完成的任务单状态不是已完成或已取消- rqrq
// 2. 根据盘点类型确定推送策略 - rqrq
String countType = header.getCountType();
boolean isCycle = CountHeader.COUNT_TYPE_CYCLE.equals(countType);
// 2.1 确定最大允许的并发任务数 - rqrq
int maxAllowedTasks = isCycle ? 1 : 2; // 循环盘点最多1个手工盘点最多2个
// 2.2 确定每批推送数量 - rqrq
int maxCount = isCycle ? 10 : 5; // 循环盘点每批10个手工盘点每批5个
log.info("盘点类型: {}, 最大并发任务数: {}, 单批推送数量: {}",
countType, maxAllowedTasks, maxCount);
// 3. 校验未完成任务数根据盘点类型判断- rqrq
int uncompletedTaskCount = baseMapper.countUncompletedTask(query.getSite(), query.getCountNo());
if (uncompletedTaskCount > 0) {
throw new RuntimeException("当前盘点单存在" + uncompletedTaskCount + "个未完成的任务单,请等待任务完成后再继续下达");
log.info("当前未完成任务数: {}", uncompletedTaskCount);
// 3.1 判断是否达到并发上限 - rqrq
if (uncompletedTaskCount >= maxAllowedTasks) {
String msg = String.format("当前盘点单存在%d个未完成的任务单,%s最多允许%d个任务同时流转,暂不推送下一批",
uncompletedTaskCount, isCycle ? "循环盘点" : "手工盘点", maxAllowedTasks);
log.info(msg);
return 0; // 不抛异常直接返回0因为是自动触发不需要中断流程
}
log.info("校验通过,无未完成的任务单");
log.info("校验通过,当前未完成任务数({})未达到上限({}), 可以推送下一批",
uncompletedTaskCount, maxAllowedTasks);
// 3. 执行推送 - rqrq
int pushedCount = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), 10);
// 4. 执行推送 - rqrq
int pushedCount = doPushPallets(query.getSite(), query.getCountNo(), query.getUsername(), maxCount);
if (pushedCount > 0) {
log.info("continuePushCount 结束,本次推送栈板数: {}", pushedCount);
} else {
log.info("continuePushCount 结束,本次未推送(可能无更多待推送托盘)");
}
return pushedCount;
}
@ -2393,12 +2539,69 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
}
}
/**
* @Description 盘点完成后处理任务单更新任务状态触发下一批推送- rqrq
*
* <p><b>调用时机</b></p>
* <pre>
* - PDA盘点完成pdaSubmitCount
* - RFID龙门架盘点完成handleCountRfidCallback
* - 快速盘点完成pdaQuickSubmitCount
* </pre>
*
* <p><b>业务逻辑重要请仔细阅读</b></p>
* <pre>
* 处理流程
* 1. 查询该栈板关联的任务号count_pallet.task_no
* 2. 如果没有任务号说明不是通过推送出库的跳过处理
* 例如手工盘点中手动添加的托盘不走推送流程
* 3. 更新wms_order_task_detail状态为"已完成"
* 4. 检查该任务的所有明细是否都已完成
* 5. 如果都完成了更新wms_order_task主表状态为"已完成"
* 6. 触发continuePushCount自动推送下一批
*
* 自动流转机制
* - 任务的所有托盘盘点完成后自动推送下一批
* - 循环盘点推送10个托盘
* - 手工盘点推送5个托盘但受2个任务并发限制
* - 形成"推送→盘点→推送→盘点"的自动循环
*
* 并发场景 - 手工盘点
* 场景1任务1和任务2同时执行
* - 任务1的最后一个托盘盘点完成 触发推送任务3
* - 任务2还在执行
* - 结果任务2 + 任务3 并行2个任务
*
* 场景2已有2个任务在执行
* - 任务2的最后一个托盘盘点完成 尝试推送
* - 任务3还在执行
* - continuePushCount检查已有1个未完成任务<2可以推送
* - 如果有剩余托盘推送任务4
* - 如果无剩余托盘返回0
*
* 防重复调用
* - 检查主表状态如果已是"已完成"跳过触发推送
* - 避免重复调用导致重复推送
*
* 异常处理
* - continuePushCount失败不抛异常
* - 避免影响当前托盘的盘点结果保存
* - 只记录日志不中断流程
* </pre>
*
* @param site 工厂编码
* @param countNo 盘点单号
* @param palletId 托盘号
* @param username 用户名
* @author rqrq
*/
private void handleTaskAfterCount(String site, String countNo, String palletId, String username) {
// 1. 查询该栈板关联的任务号 - rqrq
String taskNo = baseMapper.getTaskNoByPallet(site, countNo, palletId);
if (!StringUtils.hasText(taskNo)) {
// 没有任务号说明不是通过推送出库的不需要处理任务单 - rqrq
// 例如手工盘点中手动添加的托盘不走推送流程 - rqrq
log.info("该栈板没有关联任务号,跳过任务单处理,palletId: {}", palletId);
return;
}
@ -2409,26 +2612,45 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl<PhysicalInventoryM
baseMapper.updateTaskDetailStatusByPallet(site, taskNo, palletId);
log.info("已更新wms_order_task_detail状态为已完成,palletId: {}", palletId);
// 3. 检查该任务的所有明细是否都已完成且主表状态未完成时才触发下一批推送 - rqrq
// 3. 检查该任务的所有明细是否都已完成 - rqrq
String taskStatus = baseMapper.getWmsOrderTaskStatus(site, taskNo);
if ("已完成".equals(taskStatus)) {
// 任务已完成说明是重复调用不再触发下一批推送 - rqrq
log.info("wms_order_task已是已完成状态,跳过触发下一批推送(可能是重复调用)");
} else {
return;
}
// 3.1 查询未完成的明细数量 - rqrq
int uncompleteCount = baseMapper.checkAllTaskDetailCompletedByTaskNo(site, taskNo);
log.info("该任务未完成的明细数量: {}", uncompleteCount);
if (uncompleteCount == 0) {
// 所有明细都已完成更新主表状态 - rqrq
log.info("该任务所有明细已完成,准备更新主表状态并触发下一批推送");
baseMapper.updateTaskStatusCompleted(site, taskNo);
log.info("该任务所有明细已完成,主表状态已更新为已完成");
log.info("主表状态已更新为已完成");
// 4. 触发下一批推送 - rqrq
// 4. 触发下一批推送循环盘点和手工盘点都自动推送- rqrq
CountHeaderData query = new CountHeaderData();
query.setSite(site);
query.setCountNo(countNo);
query.setUsername(username);
try {
log.info("开始触发下一批推送");
int pushedCount = continuePushCount(query);
log.info("触发下一批推送,推送数量: {}", pushedCount);
if (pushedCount > 0) {
log.info("✅ 触发下一批推送成功,推送数量: {}", pushedCount);
} else {
log.info("ℹ️ 当前未推送(可能已达到并发上限或无待推送托盘)");
}
} catch (Exception e) {
// 不抛异常避免影响当前托盘的盘点结果保存 - rqrq
log.error("❌ 触发下一批推送失败,但不影响当前托盘盘点结果: {}", e.getMessage(), e);
}
} else {
log.info("该任务还有{}个明细未完成,等待其他托盘盘点", uncompleteCount);
}
}
}
Loading…
Cancel
Save