liu
8 小时以前 66517edf1de22c8203daa256abffddae43bae704
feat(mes): 袋码绑定托盘免先录生产批次

去掉「赋码管理 → 绑定托盘」必须先录入生产批次的限制:
- 未录入批次的袋码可绑临时托盘,也可绑某批次的正式托盘;后者由后端
先复用「录入批次」逻辑补录批次再绑定,任一步失败整体回滚
- 已录入批次的袋码仍只能绑同批次托盘
- bindBatch 抽出私有 doBindBatch 供绑定托盘复用,保证批次准入校验、
品种一致性校验、批次汇总回写与完整性记录只有一份
- 加锁顺序统一为「批次 → 袋码 → 托盘」,与录入批次一致,避免两个入口
并发时交叉等待死锁
- 新增 GET /mes/pro/pallet/bindable-list,按品种返回可绑的临时托盘与
正式托盘(排除已作废),复用 mes:pro-pallet:query 权限,无 DDL 变更

解绑口径:只解除托盘关系,批次归属不回退。

Co-Authored-By: Claude <noreply@anthropic.com>
已添加1个文件
已修改7个文件
183 ■■■■■ 文件已修改
docs/mes_bag_code_bind_pallet_without_batch_integration.md 80 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_bag_code_integration.md 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_pro_batch_bag_code_integration.md 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/MesProPalletController.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java 54 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletService.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletServiceImpl.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_bag_code_bind_pallet_without_batch_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,80 @@
# è¢‹ç ç»‘定托盘(免先录批次) - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- èµ‹ç ç®¡ç†ï¼ˆè¢‹ç åˆ—表)→ å·¥å…·æ ã€Œç»‘定托盘」按钮 + ã€Œè¢‹ç ç»‘定托盘」弹窗
## å£å¾„变更
原「绑定托盘」要求所选袋码先录入生产批次,否则提示「请先录入生产批次,再绑定托盘」。现去掉该前置限制,按勾选袋码的批次状态分流:
| æ‰€é€‰è¢‹ç  | å¯é€‰æ‰˜ç›˜ | ç»‘定结果 |
|---|---|---|
| å‡å·²å½•入同一批次 | è¯¥æ‰¹æ¬¡çš„æ‰˜ç›˜ | ä»…绑定托盘(与改造前一致) |
| å‡æœªå½•入批次 | è¯¥å“ç§å¯ç”¨çš„全部托盘(临时托盘 + æ­£å¼æ‰˜ç›˜ï¼‰ | ç»‘到临时托盘:袋码保持未录入批次;绑到某批次的正式托盘:先自动把袋码补录进该批次,再绑定 |
| æ··åˆï¼ˆéƒ¨åˆ†å·²å½•批次、部分未录) | â€” | å‰ç«¯æ‹¦æˆªï¼Œæç¤ºåˆ†å¼€æ“ä½œ |
**解绑不回退批次**:袋码因绑托盘而补录进批次后,后续「解绑托盘」只解除托盘关系,批次归属保留,与「编辑批次取消勾选托盘不回退状态」口径一致。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| GET | /mes/pro/pallet/bindable-list | **新增**:袋码绑定托盘时的可选目标托盘列表(临时托盘 + æ­£å¼æ‰˜ç›˜ï¼ŒæŽ’除已作废) |
| PUT | /mes/pro/bag-code/bind-pallet | å¤ç”¨ï¼Œè¯·æ±‚与响应结构不变 |
**请求参数(新增接口):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| itemId | Long | å¦ | è¢‹ç å“ç§ç¼–号。传入后仅返回该品种的托盘,以及尚未确定品种的预生成临时托盘(`item_id` ä¸ºç©ºï¼Œç»‘定袋码时回填品种) |
**响应:** `MesProPalletRespVO` æ•°ç»„,关键字段:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| id | Long | æ‰˜ç›˜ç¼–号(提交绑定用) |
| code | String | æ­£å¼æ‰˜ç›˜ç ï¼›ä¸´æ—¶æ‰˜ç›˜ä¸ºç©º |
| tempCode | String | ä¸´æ—¶æ‰˜ç›˜ç ï¼›`code` ä¸ºç©ºæ—¶ç”¨å®ƒå±•示 |
| batchId / batchCode | Long / String | å·²ç»‘定的生产批次;临时托盘为空 |
| itemId / itemName | Long / String | å“ç§ |
| bagCount | Integer | å·²è£…袋数 |
| capacity | Integer | å¯è£…袋数 |
| status | Integer | æ‰˜ç›˜çŠ¶æ€ï¼ˆå­—å…¸ `mes_pro_pallet_status`),已排除作废(20) |
## æŽ¥å£è°ƒç”¨æ—¶åº
1. ç”¨æˆ·å‹¾é€‰è¢‹ç ï¼Œç‚¹å‡»ã€Œç»‘定托盘」。
2. æŒ‰å‹¾é€‰è¢‹ç çš„æ‰¹æ¬¡çŠ¶æ€é€‰æ‹©æ‰˜ç›˜æ•°æ®æºï¼š
   - éƒ½å·²å½•入同一批次 â†’ `GET /mes/pro/pallet/simple-list?batchId=`
   - éƒ½æœªå½•入批次 â†’ `GET /mes/pro/pallet/bindable-list?itemId=`,`itemId` å–所选袋码的品种
3. æäº¤ `PUT /mes/pro/bag-code/bind-pallet`,body ä¸º `{ palletId, ids }`。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|---|---|---|
| batchCode | å¼¹çª—「生产批次」输入框(只读) | å·²å½•入批次显示批次号;未录入批次显示「未录入批次」 |
| bagCount | å¼¹çª—「绑定袋数」输入框(只读) | æœ¬æ¬¡å‹¾é€‰çš„袋码数量 |
| code / tempCode | æ‰˜ç›˜ä¸‹æ‹‰é€‰é¡¹ | æ­£å¼æ‰˜ç›˜å– `code`,临时托盘回退到 `tempCode` |
| batchCode | æ‰˜ç›˜ä¸‹æ‹‰é€‰é¡¹ | æœªå½•入批次场景下显示「批次 xxx」或「临时托盘」,便于在混合列表中分辨 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|---|---|
| è¢‹ç å·²å½•入批次,托盘同批次 | å…è®¸ |
| è¢‹ç å·²å½•入批次,托盘跨批次 | æ‹’绝:袋码与托盘不属于同一生产批次,不允许绑定 |
| è¢‹ç æœªå½•入批次 + ä¸´æ—¶æ‰˜ç›˜ | å…è®¸ï¼›è¢‹ç ä¿æŒæœªå½•入批次,后续「录入批次」时按既有级联逻辑把该临时托盘升为正式托盘码 |
| è¢‹ç æœªå½•入批次 + æŸæ‰¹æ¬¡æ­£å¼æ‰˜ç›˜ | å…è®¸ï¼›åŽç«¯å…ˆå¤ç”¨ã€Œå½•入批次」逻辑把袋码补录进该批次,再绑定托盘 |
| è¢‹ç æœªå½•入批次 + éžç”Ÿäº§ä¸­æ‰¹æ¬¡ / æœªè¡¥å½•批次 | æ‹’绝:生产批次不是生产中状态,不允许生成袋码/托盘码;或生产批次尚未补录正式信息 |
| è¢‹ç æœªå½•入批次 + å“ç§ä¸Žæ‰¹æ¬¡ä¸ä¸€è‡´ | æ‹’绝:袋码/托盘必须与批次品种一致 |
| æ‰˜ç›˜æœªç»‘定批次 + è¢‹ç å·²å½•入批次 | æ‹’绝:所选袋码已录入生产批次,请改选该批次下的托盘 |
| è§£ç»‘托盘 | åªè§£é™¤æ‰˜ç›˜å…³ç³»ï¼Œæ‰¹æ¬¡å½’属不回退 |
## æ³¨æ„äº‹é¡¹
- ã€Œè¡¥å½•批次 + ç»‘定托盘」在同一事务内完成,任一步校验失败整体回滚,不会出现袋码已进批次但未绑托盘。
- æœªå½•入批次场景下托盘下拉可能较长,请保持 `showSearch` å¹¶æŒ‰è¢‹ç å“ç§ä¼  `itemId` æ”¶çª„范围。
- `bindable-list` å·²æŽ’除作废托盘;`simple-list` æœªæŽ’除,原有的前端作废过滤需保留。
- è¢‹ç ä¸Žæ‰˜ç›˜å¿…须同品种:`bindable-list` æŒ‰ `itemId` è¿‡æ»¤ï¼Œä½†åŽç«¯ä»ä¼šäºŒæ¬¡æ ¡éªŒï¼Œå‰ç«¯ä¸å¿…据此做硬拦截。
docs/mes_bag_code_integration.md
@@ -112,13 +112,13 @@
| æ‰˜ç›˜ç»‘定批次 | ä¸´æ—¶æ‰˜ç›˜ä»…能绑定已正式补录且生产中的批次;跨批次、重复绑定被拒绝 |
| ä¸´æ—¶æ‰˜ç›˜å‡ºåº“ | æœªç»‘定批次的托盘禁止参与销售出库等业务操作 |
| è¢‹ç å½•入批次 | ä»…允许生产中的正式批次;临时批次不可作为录入目标 |
| è¢‹ç ç»‘定托盘 | åŒæ–¹å¿…须为同一正式批次;未绑定批次的临时托盘不可参与绑定 |
| è¢‹ç ç»‘定托盘 | æœªå½•入批次的袋码可绑临时托盘,也可绑某批次的正式托盘(此时先自动补录批次再绑定);已录入批次的袋码只能绑同批次托盘 |
| äºŒç»´ç å¯¼å‡º | ä¸´æ—¶æ‰¹æ¬¡/临时托盘按临时码导出;业务信息为空时不显示虚构内容 |
| æ‰«ç è¯†åˆ« | `PB-YYYYYYYY`→批次、`PL-YYYYYYYY`→托盘、`批次号-PXX`→托盘、`BAG-YYYYYYYY`→袋码;正式批次号/托盘码照常识别 |
## æ³¨æ„äº‹é¡¹
- å‰ç«¯è°ƒç”¨é¡ºåºï¼šæ‰¹æ¬¡â€œé¢„生成 â†’ è¡¥å½•信息 â†’ å¼€å§‹ç”Ÿäº§â€ï¼›æ‰˜ç›˜â€œé¢„生成 â†’ ç»‘定批次 â†’ ç»‘定袋码”;袋码“生成 â†’ å½•入批次 â†’ ç»‘定托盘”。
- å‰ç«¯è°ƒç”¨é¡ºåºï¼šæ‰¹æ¬¡â€œé¢„生成 â†’ è¡¥å½•信息 â†’ å¼€å§‹ç”Ÿäº§â€ï¼›æ‰˜ç›˜â€œé¢„生成 â†’ ç»‘定批次 â†’ ç»‘定袋码”;袋码“生成 â†’ ç»‘定托盘 â†’ å½•入批次”或“生成 â†’ å½•入批次 â†’ ç»‘定托盘”均可(绑定分批次的正式托盘时会自动补录批次)。
- æ‰¹æ¬¡æ­£å¼åŒ–后 `tempCode` ä¸åˆ é™¤ï¼ŒåŽ†å²æ‰“å°çš„ä¸´æ—¶äºŒç»´ç æ‰«ç ä»èƒ½è¿½æº¯ï¼Œå‹¿ç”¨æ­£å¼ç è¦†ç›–ä¸´æ—¶ç ã€‚
- ä¸´æ—¶æ‰˜ç›˜åœ¨ç»‘定批次前不可被袋码绑定、不可销售出库;批量绑定需先勾选多条临时托盘记录。
- ä¸´æ—¶æ‰˜ç›˜åœ¨ç»‘定批次前可被未录入批次的袋码绑定(不可销售出库);批量绑定需先勾选多条临时托盘记录。
- è¡¥å½•/绑定失败时保留当前选择,避免误以为已完成业务关联。
docs/mes_pro_batch_bag_code_integration.md
@@ -249,7 +249,7 @@
| æ‰¹æ¬¡ä¿®æ”¹ | ä»…草稿/生产中可修改;修改品种或产线编码会自动重新生成批次号 |
| æ‰¹æ¬¡åˆ é™¤ | ä»…草稿/已作废可删除;已生成袋码或托盘码的批次不允许删除 |
| ç”Ÿæˆç»„删除 | ä»…组内袋码全部为"已生成"且未绑定托盘时允许;删除后批次袋码数量自动回减 |
| è¢‹ç ç»‘定托盘 | è¢‹ç ä¸Žæ‰˜ç›˜å¿…须属于同一批次;袋码未绑定过托盘;绑定后总数不超过托盘可装袋数 |
| è¢‹ç ç»‘定托盘 | ä¸è¦æ±‚袋码先录入批次:未录入批次的袋码可绑临时托盘,也可绑某批次的正式托盘(此时先自动补录批次再绑定);已录入批次的袋码只能绑同批次托盘;袋码未绑定过托盘;绑定后总数不超过托盘可装袋数 |
| å¯¼å‡ºå³å°åˆ· | å¯¼å‡º Excel åŽï¼Œå¯¼å‡ºçš„"已生成"袋码自动变为"已导出印刷"并记录时间 |
| äºŒç»´ç å†…容 | äºŒç»´ç å†…容 = è¢‹ç å­—符串(如 PC20260818L01NY001-0001),扫码后调 get-by-code åæŸ¥ |
@@ -261,7 +261,7 @@
- æ‰¹æ¬¡å·ã€è¢‹ç ã€æ‰˜ç›˜ç å‡ç”±åŽç«¯è‡ªåŠ¨ç”Ÿæˆï¼Œ**前端表单不提供任何编码输入框**。
- ç”Ÿæˆè¢‹ç /托盘码前,先用 `GET /mes/pro/batch/simple-list` è®©ç”¨æˆ·é€‰æ‹©æ‰¹æ¬¡ï¼ˆåªå…è®¸é€‰æ‹©"生产中"状态的批次,前端可按 status=20 è¿‡æ»¤ï¼‰ã€‚
- ç»‘定托盘时,托盘下拉应调用 `GET /mes/pro/pallet/simple-list?batchId=`,确保托盘与袋码同批次。
- ç»‘定托盘时,托盘下拉按袋码批次状态分流:已录入批次用 `GET /mes/pro/pallet/simple-list?batchId=`;未录入批次用 `GET /mes/pro/pallet/bindable-list?itemId=`。详见 `mes_bag_code_bind_pallet_without_batch_integration.md`。
- ç”Ÿæˆè¢‹ç çš„ `count` ä¸ºæ‰‹åŠ¨è¾“å…¥çš„æœ¬æ¬¡è¢‹æ•°ï¼Œå¯èƒ½åˆ†å¤šæ¬¡ç”Ÿæˆï¼Œåºå·è‡ªåŠ¨æŽ¥ç»­ï¼Œä¸ä¼šé‡å¤ã€‚
- æ‰«ç è¿½æº¯æŽ¥å£å¯¹ä¸å­˜åœ¨çš„袋码返回错误(错误码 1040752000),前端需提示"袋码不存在"。
- å¯¼å‡ºè¢‹ç  Excel ä¼šæ”¹å˜è¢‹ç çŠ¶æ€ï¼ˆå·²ç”Ÿæˆ â†’ å·²å¯¼å‡ºå°åˆ·ï¼‰ï¼Œå¯¼å‡ºåŽè¯·åˆ·æ–°åˆ—表;重复导出同一数据包不会重复变更状态。
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/MesProPalletController.java
@@ -163,6 +163,16 @@
        return success(buildPalletRespVOList(list));
    }
    @GetMapping("/bindable-list")
    @Operation(summary = "获得袋码绑定托盘时可选的目标托盘列表(临时托盘 + æ­£å¼æ‰˜ç›˜ï¼ŒæŽ’除已作废)")
    @Parameter(name = "itemId", description = "袋码品种编号(可选,用于过滤非同品种托盘)", example = "1024")
    @PreAuthorize("@ss.hasPermission('mes:pro-pallet:query')")
    public CommonResult<List<MesProPalletRespVO>> getBindablePalletList(
            @RequestParam(value = "itemId", required = false) Long itemId) {
        List<MesProPalletDO> list = palletService.getBindablePalletList(itemId);
        return success(buildPalletRespVOList(list));
    }
    @GetMapping("/export-excel")
    @Operation(summary = "导出托盘码 Excel")
    @PreAuthorize("@ss.hasPermission('mes:pro-pallet:export')")
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/pallet/MesProPalletMapper.java
@@ -56,6 +56,22 @@
                .orderByAsc(MesProPalletDO::getId));
    }
    /**
     * æŸ¥è¯¢è¢‹ç ç»‘定托盘时可选的目标托盘:未作废的临时托盘与正式托盘。
     * <p>
     * ä¼ å…¥å“ç§æ—¶ä»…返回该品种的托盘,以及尚未确定品种的预生成临时托盘(item_id ä¸ºç©ºï¼Œç»‘定袋码时回填品种)。
     *
     * @param itemId è¢‹ç å“ç§ç¼–号,可为空
     * @return æ‰˜ç›˜åˆ—表
     */
    default List<MesProPalletDO> selectBindableList(Long itemId) {
        return selectList(new LambdaQueryWrapperX<MesProPalletDO>()
                .ne(MesProPalletDO::getStatus, MesProPalletStatusEnum.CANCELED.getStatus())
                .and(itemId != null, q -> q.eq(MesProPalletDO::getItemId, itemId)
                        .or().isNull(MesProPalletDO::getItemId))
                .orderByDesc(MesProPalletDO::getId));
    }
    default PageResult<MesProPalletDO> selectPage(MesProPalletPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesProPalletDO>()
                .likeIfPresent(MesProPalletDO::getBusinessCode, reqVO.getBusinessCode())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
@@ -223,7 +223,20 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindBatch(MesProBagCodeBindBatchReqVO bindReqVO) {
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(bindReqVO.getBatchId());
        doBindBatch(bindReqVO.getBatchId(), bindReqVO.getIds());
    }
    /**
     * å°†è¢‹ç å½•入指定批次,加锁顺序为「批次 â†’ è¢‹ç  â†’ æ‰˜ç›˜ã€ã€‚
     * <p>
     * ã€Œå½•入批次」与「绑定托盘」共用此实现:绑定托盘时若托盘已属某批次、而所选袋码尚未录入批次,
     * å…ˆæŒ‰æœ¬æ–¹æ³•完成录入,从而保证批次准入校验、品种一致性校验、批次汇总回写与完整性记录只有一份。
     *
     * @param batchId ç”Ÿäº§æ‰¹æ¬¡ç¼–号
     * @param rawIds  å¾…录入的袋码编号(未去重)
     */
    private void doBindBatch(Long batchId, List<Long> rawIds) {
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(batchId);
        if (batch == null) {
            throw exception(PRO_BATCH_NOT_EXISTS);
        }
@@ -234,7 +247,7 @@
        if (StrUtil.isEmpty(batch.getCode())) {
            throw exception(PRO_BATCH_NOT_FILLED);
        }
        List<Long> ids = bindReqVO.getIds().stream().distinct().toList();
        List<Long> ids = rawIds.stream().distinct().toList();
        List<MesProBagCodeDO> bagCodes = bagCodeMapper.selectListByIdsForUpdate(ids);
        if (bagCodes.size() != ids.size() || bagCodes.stream().anyMatch(code ->
                code.getBatchId() != null
@@ -459,13 +472,40 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindPallet(MesProBagCodeBindReqVO bindReqVO) {
        // 1. é”å®šæ‰˜ç›˜ï¼Œé¿å…ä¸Žè‡ªåŠ¨å½’é›†å¹¶å‘ä¿®æ”¹è¢‹æ•°
        // 1. æ— é”é¢„读托盘与所选袋码,判断是否需要先为未录入批次的袋码补录批次。
        //    åŠ é”é¡ºåºå¿…é¡»ä¸Žã€Œå½•å…¥æ‰¹æ¬¡ã€ä¸€è‡´ï¼ˆæ‰¹æ¬¡ â†’ è¢‹ç  â†’ æ‰˜ç›˜ï¼‰ï¼Œå¦åˆ™ä¸¤ä¸ªå…¥å£å¹¶å‘时交叉等待会死锁。
        List<Long> ids = bindReqVO.getIds().stream().distinct().toList();
        MesProPalletDO palletSnapshot = palletMapper.selectById(bindReqVO.getPalletId());
        if (palletSnapshot == null) {
            throw exception(PRO_PALLET_NOT_EXISTS);
        }
        List<MesProBagCodeDO> snapshots = bagCodeMapper.selectListByIds(ids);
        if (snapshots.size() != ids.size()) {
            throw exception(PRO_BAG_CODE_NOT_EXISTS);
        }
        // 2. æ‰˜ç›˜æœªç»‘定批次时,袋码也必须未录入批次(反向不成立:未录入批次的袋码可绑到任意托盘)
        if (palletSnapshot.getBatchId() == null
                && snapshots.stream().anyMatch(code -> code.getBatchId() != null)) {
            throw new cn.iocoder.yudao.framework.common.exception.ServiceException(400,
                    "所选袋码已录入生产批次,请改选该批次下的托盘");
        }
        // 3. æ‰˜ç›˜å·²ç»‘定批次、且存在未录入批次的袋码时,复用「录入批次」逻辑补录
        if (palletSnapshot.getBatchId() != null) {
            List<Long> unboundIds = snapshots.stream()
                    .filter(code -> code.getBatchId() == null && code.getPalletId() == null
                            && !MesProBagCodeStatusEnum.CANCELED.getStatus().equals(code.getStatus()))
                    .map(MesProBagCodeDO::getId).toList();
            if (!unboundIds.isEmpty()) {
                doBindBatch(palletSnapshot.getBatchId(), unboundIds);
            }
        }
        // 4. é”å®šæ‰˜ç›˜ï¼Œé¿å…ä¸Žè‡ªåŠ¨å½’é›†å¹¶å‘ä¿®æ”¹è¢‹æ•°
        MesProPalletDO pallet = palletMapper.selectByIdForUpdate(bindReqVO.getPalletId());
        if (pallet == null) {
            throw exception(PRO_PALLET_NOT_EXISTS);
        }
        // 2. é”å®šå¹¶æ ¡éªŒè¢‹ç 
        List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(bindReqVO.getIds(), pallet, true);
        // 5. é”å®šå¹¶æ ¡éªŒè¢‹ç ï¼ˆå½•入批次后,袋码与托盘必然同批次)
        List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(ids, pallet, true);
        Long itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.resolve(pallet,
                bagCodeMapper.selectListByPalletId(pallet.getId()));
        for (MesProBagCodeDO bag : bagCodes) {
@@ -473,9 +513,9 @@
            itemId = cn.iocoder.yudao.module.mes.service.pro.pallet.MesProPalletItemResolver.merge(itemId, bag.getItemId());
        }
        palletMapper.updateById(new MesProPalletDO().setId(pallet.getId()).setItemId(itemId));
        // 3. ç»‘定袋码并累加托盘已装袋数(托盘无容量上限)
        // 6. ç»‘定袋码并累加托盘已装袋数(托盘无容量上限)
        int currentCount = pallet.getBagCount() == null ? 0 : pallet.getBagCount();
        bagCodeMapper.bindPalletByIds(bindReqVO.getIds(), pallet.getId(), StrUtil.isNotBlank(pallet.getCode()) ? pallet.getCode() : pallet.getTempCode());
        bagCodeMapper.bindPalletByIds(ids, pallet.getId(), StrUtil.isNotBlank(pallet.getCode()) ? pallet.getCode() : pallet.getTempCode());
        palletService.updatePalletBagCount(pallet.getId(), currentCount + bagCodes.size());
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletService.java
@@ -102,6 +102,14 @@
    List<MesProPalletDO> getUnboundPalletList();
    /**
     * èŽ·å¾—è¢‹ç ç»‘å®šæ‰˜ç›˜æ—¶å¯é€‰çš„ç›®æ ‡æ‰˜ç›˜åˆ—è¡¨ï¼ˆä¸´æ—¶æ‰˜ç›˜ + æ­£å¼æ‰˜ç›˜ï¼ŒæŽ’除已作废)
     *
     * @param itemId è¢‹ç å“ç§ç¼–号,用于过滤非同品种托盘;可为空
     * @return æ‰˜ç›˜ç åˆ—表
     */
    List<MesProPalletDO> getBindablePalletList(Long itemId);
    /**
     * èŽ·å¾—æ‰˜ç›˜ç åˆ—è¡¨ï¼ˆæŒ‰ç¼–å·åˆ—è¡¨ï¼‰
     *
     * @param ids ç¼–号列表
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletServiceImpl.java
@@ -290,6 +290,11 @@
    }
    @Override
    public List<MesProPalletDO> getBindablePalletList(Long itemId) {
        return palletMapper.selectBindableList(itemId);
    }
    @Override
    public List<MesProPalletDO> getPalletListByIds(Collection<Long> ids) {
        if (CollUtil.isEmpty(ids)) {
            return List.of();