Browse Source

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

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

296
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个栈板 → 任务1(5个)+ 任务2(3个)
* 15个栈板 → 任务1(5个)+ 任务2(5个),剩余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 推送参数(包含site、countNo、username)
* @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个托盘】:
* 首次:推送任务1(5个)+ 任务2(5个)
* ↓
* 任务1完成 → continuePushCount触发
* ├─ 检查未完成任务数:1个(任务2)
* ├─ 判断:1 < 2(允许推送)✅
* └─ 推送任务3(5个)
* 现在:任务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 推送参数(包含site、countNo、username)
* @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);
log.info("continuePushCount 结束,本次推送栈板数: {}", pushedCount);
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 {
int uncompleteCount = baseMapper.checkAllTaskDetailCompletedByTaskNo(site, taskNo);
if (uncompleteCount == 0) {
// 所有明细都已完成,更新主表状态 - rqrq
baseMapper.updateTaskStatusCompleted(site, taskNo);
log.info("该任务所有明细已完成,主表状态已更新为已完成");
// 4. 触发下一批推送 - rqrq
CountHeaderData query = new CountHeaderData();
query.setSite(site);
query.setCountNo(countNo);
query.setUsername(username);
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("主表状态已更新为已完成");
// 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