diff --git a/src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java b/src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java index 3e6da6c..e944b9b 100644 --- a/src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java +++ b/src/main/java/com/gaotao/modules/check/service/impl/PhysicalInventoryServiceImpl.java @@ -756,16 +756,41 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl业务逻辑:

+ *

业务逻辑(重要!请仔细阅读):

*
+     * 【循环盘点(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推送下一批
+     * - 形成"推送→盘点→推送→盘点"的自动循环
+     * - 直到所有托盘都推送完成
      * 
+ * + * @param query 推送参数(包含site、countNo、username) + * @return int 本次推送的栈板总数 + * @author rqrq */ @Override @Transactional(rollbackFor = Exception.class) @@ -781,11 +806,61 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl 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业务逻辑:

+ *

业务逻辑(重要!请仔细阅读):

*
+     * 【调用时机】:
+     * - 由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
      * 
+ * + * @param query 推送参数(包含site、countNo、username) + * @return int 本次推送的栈板数(0表示未推送或无更多托盘) + * @author rqrq */ @Override @Transactional(rollbackFor = Exception.class) @@ -825,17 +947,41 @@ public class PhysicalInventoryServiceImpl extends ServiceImpl 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调用时机:

+ *
+     * - PDA盘点完成(pdaSubmitCount)
+     * - RFID龙门架盘点完成(handleCountRfidCallback)
+     * - 快速盘点完成(pdaQuickSubmitCount)
+     * 
+ * + *

业务逻辑(重要!请仔细阅读):

+ *
+     * 【处理流程】:
+     * 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失败不抛异常
+     * - 避免影响当前托盘的盘点结果保存
+     * - 只记录日志,不中断流程
+     * 
+ * + * @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 0) { + log.info("✅ 触发下一批推送成功,推送数量: {}", pushedCount); + } else { + log.info("ℹ️ 当前未推送(可能已达到并发上限或无待推送托盘)"); + } + } catch (Exception e) { + // 不抛异常,避免影响当前托盘的盘点结果保存 - rqrq + log.error("❌ 触发下一批推送失败,但不影响当前托盘盘点结果: {}", e.getMessage(), e); } + } else { + log.info("该任务还有{}个明细未完成,等待其他托盘盘点", uncompleteCount); } } }