14 小时以前 476d70b2f294f36a9dbd8d0c3cf5d1b75ba34c66
feat(mes): 新增包装袋型管理和托盘自动生成功能

- 在 DictTypeConstants 中添加包装袋型字典常量
- 在 ErrorCodeConstants 中添加袋型相关错误码定义
- 修改 MesProBagCodeController 的 generateBagCodes 接口返回托盘标签数据
- 在 MesProBagCodeMapper 中添加查询未绑定有效袋码的方法
- 更新 MesProBagCodeService 接口返回托盘生成结果
- 在 MesProBatchDO 中添加 bagType 字段存储包装袋型
- 在 MesProBatchRespVO 和 SaveReqVO 中添加包装袋型字段
- 实现批次袋型校验和修改限制逻辑
- 在 MesProPalletDO 中添加 autoGenerated 自动生成标识字段
- 在 MesProPalletRespVO 中添加自动生成标识字段
- 实现 MesProPalletService 的 autoCreatePallets 自动托盘生成功能
- 添加 MesProBagCodeGenerateRespVO 和 MesProPalletAutoPrintRespVO 数据传输对象
- 定义 MesProBagTypeEnum 枚举包含小袋大袋类型和阈值
- 提供前端联调文档和数据库迁移脚本
已添加5个文件
已修改15个文件
485 ■■■■■ 文件已修改
docs/mes_pro_pallet_auto_generation_integration.md 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_pro_pallet_auto_threshold.sql 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeGenerateRespVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchRespVO.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchSaveReqVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchTraceRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/vo/MesProPalletAutoPrintRespVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/vo/MesProPalletRespVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/batch/MesProBatchDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/pallet/MesProPalletDO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProBagTypeEnum.java 30 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java 33 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletService.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletServiceImpl.java 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_pro_pallet_auto_generation_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,144 @@
# MES æ‰˜ç›˜ç è‡ªåŠ¨ç”Ÿæˆ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§æ‰¹æ¬¡ç®¡ç†ï¼šæ–°å¢ž/编辑批次时选择包装袋型。
- è¢‹ç ç®¡ç†ï¼šç”Ÿæˆè¢‹ç æˆåŠŸåŽæŽ¥æ”¶è‡ªåŠ¨æ‰˜ç›˜æ ‡ç­¾æ•°æ®ã€‚
- æ‰˜ç›˜ç ç®¡ç†ï¼šæŸ¥çœ‹è‡ªåŠ¨ç”Ÿæˆæ‰˜ç›˜åŠå…¶ç»‘å®šè¢‹æ•°ã€‚
- äºŒç»´ç æ‰“印区域:根据标签数据一次渲染同一托盘的 3 ä»½æ ‡ç­¾å¹¶è§¦å‘一次浏览器打印。
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. åˆ›å»ºæˆ–编辑批次时,必须选择包装袋型:小袋(值 10)或大袋(值 20)。小袋自动托盘阈值为 100 è¢‹ï¼Œå¤§è¢‹è‡ªåŠ¨æ‰˜ç›˜é˜ˆå€¼ä¸º 48 è¢‹ã€‚
2. æ‰¹æ¬¡è¿›å…¥ç”Ÿäº§ä¸­åŽï¼Œè°ƒç”¨ç”Ÿæˆè¢‹ç æŽ¥å£ã€‚袋码的批次号、品种、产线、生产日期和责任人由批次自动带入。
3. åŽç«¯åœ¨åŒä¸€äº‹åŠ¡ä¸­ç»Ÿè®¡è¯¥æ‰¹æ¬¡æœ‰æ•ˆè¢‹ç ï¼šæœªé€»è¾‘åˆ é™¤ä¸”çŠ¶æ€ä¸æ˜¯å·²ä½œåºŸï¼Œå¹¶æŒ‰è¢‹åºå·å‡åºå–å®Œæ•´é˜ˆå€¼ç»„ã€‚
4. æ¯ä¸ªå®Œæ•´é˜ˆå€¼ç»„自动创建一个托盘码,并将组内全部袋码写入同一托盘的托盘编号和托盘码。未达到完整阈值的余数袋码保持未绑定。
5. è‡ªåŠ¨æ‰˜ç›˜ç”ŸæˆåŽï¼Œç”Ÿæˆè¢‹ç æŽ¥å£å“åº”ä¸­çš„ `autoPallets` è¿”回本次新建托盘标签数据。每个标签的 `copies` å›ºå®šä¸º 3,表示同一托盘二维码打印 3 ä»½ï¼Œä¸æ˜¯ç”Ÿæˆ 3 ä¸ªæ‰˜ç›˜ã€‚
6. å‰ç«¯æ”¶åˆ° `autoPallets` åŽï¼Œä¸ºæ¯ä¸ªæ‰˜ç›˜æ ‡ç­¾æ¸²æŸ“ 3 ä¸ªæ‰“印副本,并只调用一次浏览器打印。普通网页打印会打开浏览器打印流程,后端不返回静默打印成功状态。
## API
### 1. æ‰¹æ¬¡åˆ›å»º/更新
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| POST | `/mes/pro/batch/create` | åˆ›å»ºç”Ÿäº§æ‰¹æ¬¡ï¼ŒåŒ…装袋型必填 |
| PUT | `/mes/pro/batch/update` | æ›´æ–°ç”Ÿäº§æ‰¹æ¬¡ï¼›ç”Ÿæˆè¢‹ç æˆ–托盘后不允许修改袋型 |
请求参数新增:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| bagType | Integer | æ˜¯ | 10 å°è¢‹ï¼ˆé˜ˆå€¼100),20 å¤§è¢‹ï¼ˆé˜ˆå€¼48) |
响应字段新增:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| bagType | Integer | å½“前批次包装袋型 |
### 2. ç”Ÿæˆè¢‹ç å¹¶è‡ªåŠ¨ç”Ÿæˆæ‰˜ç›˜
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| POST | `/mes/pro/bag-code/generate` | ç”Ÿæˆè¢‹ç ï¼›æœ‰æ•ˆè¢‹ç è¾¾åˆ°å®Œæ•´é˜ˆå€¼æ—¶è‡ªåŠ¨ç”Ÿæˆæ‰˜ç›˜å¹¶ç»‘å®š |
请求参数:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| batchId | Long | æ˜¯ | ç”Ÿäº§ä¸­æ‰¹æ¬¡ç¼–号 |
| count | Integer | æ˜¯ | æœ¬æ¬¡ç”Ÿæˆè¢‹ç æ•°é‡ï¼Œå¤§äºŽ0 |
| remark | String | å¦ | è¢‹ç å¤‡æ³¨ |
响应 `data`:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| uuid | String | æœ¬æ¬¡è¢‹ç ç”Ÿæˆç»„ UUID |
| autoPallets | Array | æœ¬æ¬¡è‡ªåŠ¨ç”Ÿæˆæ‰˜ç›˜æ ‡ç­¾åˆ—è¡¨ï¼›æœªè·¨è¿‡å®Œæ•´é˜ˆå€¼æ—¶ä¸ºç©º |
`autoPallets` å­—段:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| id | Long | æ‰˜ç›˜ç¼–号 |
| code | String | æ‰˜ç›˜äºŒç»´ç å†…容 |
| batchId | Long | ç”Ÿäº§æ‰¹æ¬¡ç¼–号 |
| batchCode | String | æ‰¹æ¬¡å· |
| palletNo | Integer | æ‰¹æ¬¡å†…托盘序号 |
| capacity | Integer | æ‰˜ç›˜å®¹é‡ï¼Œç­‰äºŽè¢‹åž‹é˜ˆå€¼ |
| bagCount | Integer | å·²è£…袋数,自动托盘生成后等于容量 |
| copies | Integer | æ ‡ç­¾æ‰“印份数,固定为3 |
| itemId/itemCode/itemName/itemSpecification | â€” | å“ç§ä¿¡æ¯ |
| lineCode/lineName | String | äº§çº¿ä¿¡æ¯ |
| produceDate | String | ç”Ÿäº§æ—¥æœŸ |
| ownerUserId/ownerUserName | â€” | è´£ä»»äººä¿¡æ¯ |
响应示例:
```json
{
  "code": 0,
  "data": {
    "uuid": "生成组UUID",
    "autoPallets": [
      {
        "id": 1001,
        "code": "PC20260821L01A001-P01",
        "batchId": 1024,
        "batchCode": "PC20260821L01A001",
        "palletNo": 1,
        "capacity": 100,
        "bagCount": 100,
        "copies": 3,
        "itemId": 2001,
        "itemCode": "ITEM001",
        "itemName": "产品A",
        "itemSpecification": "规格A",
        "lineCode": "L01",
        "lineName": "包装一线",
        "produceDate": "2026-08-21 08:00:00",
        "ownerUserId": 1,
        "ownerUserName": "张三"
      }
    ]
  }
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|---|---|---|
| bagType | æ‰¹æ¬¡æ–°å¢ž/编辑、批次列表、批次详情 | é€šè¿‡å­—典显示小袋/大袋;批次已有袋码或托盘后只读 |
| bagQuantity | æ‰¹æ¬¡åˆ—表/详情 | æœ‰æ•ˆè¢‹ç ç´¯è®¡æ•°é‡ |
| palletQuantity | æ‰¹æ¬¡åˆ—表/详情 | å·²ç”Ÿæˆæ‰˜ç›˜æ•°é‡ï¼ŒåŒ…含手工和自动托盘 |
| autoGenerated | æ‰˜ç›˜åˆ—表/详情 | æ ‡è¯†è¯¥æ‰˜ç›˜æ˜¯å¦ç”±é˜ˆå€¼è‡ªåŠ¨ç”Ÿæˆ |
| autoPallets[].code | æ‰“印标签 | ä½œä¸ºäºŒç»´ç ç¼–码内容,同时显示托盘码文本 |
| autoPallets[].copies | æ‰“印控制 | åŒä¸€æ‰˜ç›˜æ ‡ç­¾æ‰“印3份;不要连续调用三次打印 |
| autoPallets[].batchCode | æ‰“印标签 | æ˜¾ç¤ºå…³è”批次号 |
| autoPallets[].bagCount/capacity | æ‰“印标签或详情 | è‡ªåŠ¨æ‰˜ç›˜æ­£å¸¸æƒ…å†µä¸‹ç›¸ç­‰ï¼Œå‡ä¸ºå¯¹åº”é˜ˆå€¼ |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|---|---|
| å°è¢‹ | æœ‰æ•ˆè¢‹ç æ¯ç´¯è®¡100个自动生成一个托盘 |
| å¤§è¢‹ | æœ‰æ•ˆè¢‹ç æ¯ç´¯è®¡48个自动生成一个托盘 |
| æœ‰æ•ˆè¢‹ç  | `deleted = 0` ä¸” `status <> 30`,已绑定人工托盘的袋码不参与自动归集 |
| è¢‹ç åˆ†ç»„ | æŒ‰ `bagNo` å‡åºï¼Œæ¯ç»„必须达到完整阈值;余数不绑定 |
| è‡ªåŠ¨æ‰˜ç›˜å®¹é‡ | å›ºå®šä¸ºå½“前批次阈值,不使用手工托盘容量 |
| é‡å¤ç”Ÿæˆ | åŒä¸€æ‰¹æ¬¡è¯·æ±‚串行锁定;已绑定袋码不会再次候选,重复调用不重复创建 |
| äººå·¥ç»‘定 | è‡ªåŠ¨æµç¨‹ä¸æ”¹ç»‘å·²äººå·¥ç»‘å®šè¢‹ç ï¼›äººå·¥ç»‘å®šå’Œè§£ç»‘éœ€è¦åœ¨æœåŠ¡ç«¯åŠ é” |
| ç ´æŸæ›¿æ¢ | æ–°è¢‹ç ç»§æ‰¿æ—§è¢‹ç å·²æœ‰æ‰˜ç›˜ï¼›æœ‰æ•ˆæ•°é‡ä¸å¢žåŠ ï¼Œä¸é‡å¤è§¦å‘æ–°æ‰˜ç›˜ |
| åˆ é™¤/作废 | ä½œåºŸè¢‹ç ä¸è®¡å…¥æœ‰æ•ˆæ•°ï¼›å·²ç»‘定袋码不允许通过生成组删除 |
| æ‰“印 | åŽç«¯åªè¿”回标签数据;浏览器端一次渲染3份并调用一次 `window.print()`,无法保证静默打印 |
## æ³¨æ„äº‹é¡¹
- åŽ†å²æ‰¹æ¬¡çš„ `bagType` éœ€è¦åœ¨å¯ç”¨è‡ªåŠ¨æ‰˜ç›˜å‰è¡¥é½ï¼Œä¸èƒ½ç”±åŽç«¯çŒœæµ‹ã€‚
- åªæœ‰ç”Ÿäº§ä¸­æ‰¹æ¬¡å…è®¸ç”Ÿæˆè¢‹ç å’Œè‡ªåŠ¨æ‰˜ç›˜ã€‚
- æ™®é€šæµè§ˆå™¨æ— æ³•绕过系统打印对话框;若要求无人值守打印,需要另行接入本地打印代理或打印控件。
- ä¸è¦æŠŠ ZIP ä¸‹è½½å“åº”当作自动打印结果;现有 ZIP æŽ¥å£ç»§ç»­ç”¨äºŽæ‰¹é‡å¯¼å‡ºã€‚
- è‡ªåŠ¨æ‰˜ç›˜ä¸­çš„è¢‹ç å®Œæ•´å…³è”ä»¥è¢‹ç è®°å½•çš„ `palletId`、`palletCode` å’Œæ‰˜ç›˜çš„ `bagCount` ä¸ºå‡†ã€‚
sql/mysql/mes_pro_pallet_auto_threshold.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,44 @@
-- =============================================
-- MES æ‰˜ç›˜ç è‡ªåŠ¨ç”Ÿæˆï¼ˆæŒ‰æœ‰æ•ˆè¢‹ç ç´¯è®¡é˜ˆå€¼ï¼‰è¿ç§»è„šæœ¬
-- 1. mes_pro_batch å¢žåŠ åŒ…è£…è¢‹åž‹ bag_type(小袋 10 / å¤§è¢‹ 20)
-- 2. mes_pro_pallet å¢žåŠ  auto_generated è‡ªåŠ¨ç”Ÿæˆæ ‡è¯†
-- 3. å¢žåŠ åŒ…è£…è¢‹åž‹å­—å…¸
-- MySQL 8.4 é€‚用,不包含 tenant_id
-- =============================================
SET NAMES utf8mb4;
-- ----------------------------
-- 1. ç”Ÿäº§æ‰¹æ¬¡å¢žåŠ åŒ…è£…è¢‹åž‹
-- ----------------------------
SET @batch_bag_type_exists = (SELECT COUNT(*) FROM information_schema.columns
    WHERE table_schema = DATABASE() AND table_name = 'mes_pro_batch' AND column_name = 'bag_type');
SET @batch_bag_type_sql = IF(@batch_bag_type_exists = 0,
    'ALTER TABLE mes_pro_batch ADD COLUMN bag_type TINYINT DEFAULT NULL COMMENT ''包装袋型(字典 mes_pro_bag_type:10 å°è¢‹ 20 å¤§è¢‹ï¼‰ï¼Œç”¨äºŽè‡ªåŠ¨å½’é›†æ‰˜ç›˜'' AFTER item_id',
    'SELECT 1');
PREPARE batch_bag_type_stmt FROM @batch_bag_type_sql;
EXECUTE batch_bag_type_stmt;
DEALLOCATE PREPARE batch_bag_type_stmt;
-- ----------------------------
-- 2. æ‰˜ç›˜ç è¡¨å¢žåŠ è‡ªåŠ¨ç”Ÿæˆæ ‡è¯†
-- ----------------------------
SET @pallet_auto_generated_exists = (SELECT COUNT(*) FROM information_schema.columns
    WHERE table_schema = DATABASE() AND table_name = 'mes_pro_pallet' AND column_name = 'auto_generated');
SET @pallet_auto_generated_sql = IF(@pallet_auto_generated_exists = 0,
    'ALTER TABLE mes_pro_pallet ADD COLUMN auto_generated BIT(1) NOT NULL DEFAULT b''0'' COMMENT ''是否自动生成(按有效袋码累计阈值生成,容量固定为阈值)'' AFTER bag_count',
    'SELECT 1');
PREPARE pallet_auto_generated_stmt FROM @pallet_auto_generated_sql;
EXECUTE pallet_auto_generated_stmt;
DEALLOCATE PREPARE pallet_auto_generated_stmt;
-- ----------------------------
-- 3. åŒ…装袋型字典
-- ----------------------------
INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
SELECT 'MES åŒ…装袋型', 'mes_pro_bag_type', 0, '托盘自动归集阈值判定', '1', NOW(), '1', NOW(), b'0' FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM (SELECT id FROM system_dict_type WHERE `type` = 'mes_pro_bag_type' AND deleted = 0) t);
INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
(1065364, 1, '小袋', '10', 'mes_pro_bag_type', 0, 'primary', '', '1', NOW(), '1', NOW(), b'0'),
(1065365, 2, '大袋', '20', 'mes_pro_bag_type', 0, 'warning', '', '1', NOW(), '1', NOW(), b'0');
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/MesProBagCodeController.java
@@ -10,6 +10,7 @@
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
@@ -71,9 +72,9 @@
    private MesMdmItemService mdmItemService;
    @PostMapping("/generate")
    @Operation(summary = "生成袋码数据包(一袋一码)")
    @Operation(summary = "生成袋码数据包(一袋一码),累计达到阈值时自动生成托盘码")
    @PreAuthorize("@ss.hasPermission('mes:pro-bag-code:create')")
    public CommonResult<String> generateBagCodes(@Valid @RequestBody MesProBagCodeGenerateReqVO generateReqVO) {
    public CommonResult<MesProBagCodeGenerateRespVO> generateBagCodes(@Valid @RequestBody MesProBagCodeGenerateReqVO generateReqVO) {
        return success(bagCodeService.generateBagCodes(generateReqVO));
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/bagcode/vo/MesProBagCodeGenerateRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletAutoPrintRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - MES è¢‹ç ç”Ÿæˆå“åº”")
@Data
public class MesProBagCodeGenerateRespVO {
    @Schema(description = "生成组 UUID")
    private String uuid;
    @Schema(description = "本次自动生成的托盘标签;每个标签 copies å›ºå®šä¸º 3")
    private List<MesProPalletAutoPrintRespVO> autoPallets;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchRespVO.java
@@ -30,6 +30,11 @@
    @ExcelIgnore
    private Long itemId;
    @Schema(description = "包装袋型(字典 mes_pro_bag_type)", example = "10")
    @ExcelProperty("包装袋型")
    @DictFormat(DictTypeConstants.MES_PRO_BAG_TYPE)
    private Integer bagType;
    @Schema(description = "品种编码")
    @ExcelProperty("品种编码")
    private String itemCode;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchSaveReqVO.java
@@ -24,6 +24,10 @@
    @NotNull(message = "品种不能为空")
    private Long itemId;
    @Schema(description = "包装袋型(字典 mes_pro_bag_type:10 å°è¢‹ã€20 å¤§è¢‹ï¼‰", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
    @NotNull(message = "包装袋型不能为空")
    private Integer bagType;
    @Schema(description = "产线编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
    @NotNull(message = "产线不能为空")
    private Long lineId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/batch/vo/MesProBatchTraceRespVO.java
@@ -52,6 +52,9 @@
    // ========== å“ç§ä¿¡æ¯ ==========
    @Schema(description = "包装袋型(字典 mes_pro_bag_type)", example = "10")
    private Integer bagType;
    @Schema(description = "品种(物料)编号", example = "1024")
    private Long itemId;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/vo/MesProPalletAutoPrintRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES è‡ªåŠ¨æ‰˜ç›˜æ‰“å°æ ‡ç­¾")
@Data
public class MesProPalletAutoPrintRespVO {
    @Schema(description = "托盘编号")
    private Long id;
    @Schema(description = "托盘码")
    private String code;
    @Schema(description = "批次编号")
    private Long batchId;
    @Schema(description = "批次号")
    private String batchCode;
    @Schema(description = "批次内托盘序号")
    private Integer palletNo;
    @Schema(description = "标签打印份数")
    private Integer copies;
    @Schema(description = "可装袋数")
    private Integer capacity;
    @Schema(description = "已装袋数")
    private Integer bagCount;
    @Schema(description = "品种编号")
    private Long itemId;
    @Schema(description = "品种编码")
    private String itemCode;
    @Schema(description = "品种名称")
    private String itemName;
    @Schema(description = "品种规格")
    private String itemSpecification;
    @Schema(description = "产线编码")
    private String lineCode;
    @Schema(description = "产线名称")
    private String lineName;
    @Schema(description = "生产日期")
    private LocalDateTime produceDate;
    @Schema(description = "责任人用户编号")
    private Long ownerUserId;
    @Schema(description = "责任人昵称")
    private String ownerUserName;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/pallet/vo/MesProPalletRespVO.java
@@ -45,6 +45,10 @@
    @ExcelProperty("已装袋数")
    private Integer bagCount;
    @Schema(description = "是否自动生成", example = "false")
    @ExcelIgnore
    private Boolean autoGenerated;
    @Schema(description = "状态(字典 mes_pro_pallet_status)", example = "10")
    @ExcelProperty(value = "状态", converter = DictConvert.class)
    @DictFormat(DictTypeConstants.MES_PRO_PALLET_STATUS)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/batch/MesProBatchDO.java
@@ -45,6 +45,10 @@
     */
    private Long itemId;
    /**
     * åŒ…装袋型(字典 mes_pro_bag_type)
     */
    private Integer bagType;
    /**
     * äº§çº¿ç¼–号
     * <p>
     * å…³è” {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.line.MesProLineDO#getId()}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/pallet/MesProPalletDO.java
@@ -58,6 +58,10 @@
     */
    private Integer bagCount;
    /**
     * æ˜¯å¦è‡ªåŠ¨ç”Ÿæˆï¼ˆæŒ‰æœ‰æ•ˆè¢‹ç ç´¯è®¡é˜ˆå€¼ç”Ÿæˆï¼Œå®¹é‡å›ºå®šä¸ºé˜ˆå€¼ï¼‰
     */
    private Boolean autoGenerated;
    /**
     * çŠ¶æ€
     * <p>
     * å­—å…¸ {@link DictTypeConstants#MES_PRO_PALLET_STATUS}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java
@@ -5,6 +5,7 @@
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
@@ -39,6 +40,15 @@
        return selectList(MesProBagCodeDO::getBatchId, batchId);
    }
    default List<MesProBagCodeDO> selectUnboundEffectiveListByBatchId(Long batchId) {
        return selectList(new LambdaQueryWrapperX<MesProBagCodeDO>()
                .eq(MesProBagCodeDO::getBatchId, batchId)
                .isNull(MesProBagCodeDO::getPalletId)
                .ne(MesProBagCodeDO::getStatus, MesProBagCodeStatusEnum.CANCELED.getStatus())
                .orderByAsc(MesProBagCodeDO::getBagNo)
                .last("FOR UPDATE"));
    }
    default Long selectCountByBatchId(Long batchId) {
        return selectCount(MesProBagCodeDO::getBatchId, batchId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -31,6 +31,7 @@
    String MES_PRO_TASK_STATUS = "mes_pro_task_status"; // MES ä»»åŠ¡çŠ¶æ€
    String MES_PRO_BATCH_STATUS = "mes_pro_batch_status"; // MES ç”Ÿäº§æ‰¹æ¬¡çŠ¶æ€
    String MES_PRO_BATCH_WORKER_TYPE = "mes_pro_batch_worker_type"; // MES æ‰¹æ¬¡äººå‘˜å²—位类型
    String MES_PRO_BAG_TYPE = "mes_pro_bag_type"; // MES åŒ…装袋型
    String MES_PRO_BAG_CODE_STATUS = "mes_pro_bag_code_status"; // MES è¢‹ç çŠ¶æ€
    String MES_PRO_PALLET_STATUS = "mes_pro_pallet_status"; // MES æ‰˜ç›˜ç çŠ¶æ€
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -809,6 +809,8 @@
    ErrorCode PRO_QR_EXPORT_TOO_MANY = new ErrorCode(1_040_750_007, "二维码批量导出数量超过上限");
    ErrorCode PRO_QR_IMAGE_GENERATE_ERROR = new ErrorCode(1_040_750_008, "二维码图片生成失败");
    ErrorCode PRO_QR_EXPORT_PARAMS_INVALID = new ErrorCode(1_040_750_009, "二维码批量导出缺少查询条件");
    ErrorCode PRO_BATCH_BAG_TYPE_INVALID = new ErrorCode(1_040_750_010, "包装袋型无效");
    ErrorCode PRO_BATCH_BAG_TYPE_NOT_ALLOW_CHANGE = new ErrorCode(1_040_750_011, "批次已生成袋码或托盘码,不允许修改包装袋型");
    // ========== MES ç”Ÿäº§ç®¡ç†-袋码赋码(1-040-752-000) ==========
    ErrorCode PRO_BAG_CODE_NOT_EXISTS = new ErrorCode(1_040_752_000, "袋码不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pro/MesProBagTypeEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.mes.enums.pro;
import cn.iocoder.yudao.framework.common.core.ArrayValuable;
import lombok.AllArgsConstructor;
import lombok.Getter;
import java.util.Arrays;
@Getter
@AllArgsConstructor
public enum MesProBagTypeEnum implements ArrayValuable<Integer> {
    SMALL(10, "小袋", 100),
    LARGE(20, "大袋", 48);
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesProBagTypeEnum::getType).toArray(Integer[]::new);
    private final Integer type;
    private final String name;
    private final int threshold;
    @Override
    public Integer[] array() {
        return ARRAYS;
    }
    public static MesProBagTypeEnum valueOfType(Integer type) {
        return Arrays.stream(values()).filter(item -> item.type.equals(type)).findFirst().orElse(null);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeService.java
@@ -3,6 +3,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceRespVO;
@@ -27,7 +28,7 @@
     * @param generateReqVO ç”Ÿæˆä¿¡æ¯
     * @return ç”Ÿæˆç»„ UUID(标记同一次生成的数据包)
     */
    String generateBagCodes(@Valid MesProBagCodeGenerateReqVO generateReqVO);
    MesProBagCodeGenerateRespVO generateBagCodes(@Valid MesProBagCodeGenerateReqVO generateReqVO);
    /**
     * ä½œåºŸç ´æŸè¢‹ç å¹¶åœ¨åŽŸæ‰¹æ¬¡ç”Ÿæˆç­‰é‡æ›¿æ¢è¢‹ç 
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/bagcode/MesProBagCodeServiceImpl.java
@@ -6,6 +6,7 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeBindReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeGenerateRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceItemRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.bagcode.vo.MesProBagCodeReplaceReqVO;
@@ -62,7 +63,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public String generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) {
    public MesProBagCodeGenerateRespVO generateBagCodes(MesProBagCodeGenerateReqVO generateReqVO) {
        // 1. æ ¡éªŒæ‰¹æ¬¡å­˜åœ¨ä¸”处于生产中状态
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(generateReqVO.getBatchId());
        if (batch == null) {
@@ -98,7 +99,9 @@
        batchMapper.updateById(new MesProBatchDO().setId(batch.getId())
                .setBagQuantity((batch.getBagQuantity() == null ? 0 : batch.getBagQuantity())
                        + generateReqVO.getCount()));
        return uuid;
        return new MesProBagCodeGenerateRespVO()
                .setUuid(uuid)
                .setAutoPallets(palletService.autoCreatePallets(batch.getId()));
    }
    @Override
@@ -242,10 +245,13 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void bindPallet(MesProBagCodeBindReqVO bindReqVO) {
        // 1. æ ¡éªŒæ‰˜ç›˜å­˜åœ¨
        MesProPalletDO pallet = palletService.validatePalletExists(bindReqVO.getPalletId());
        // 2. æ ¡éªŒè¢‹ç 
        List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(bindReqVO.getIds(), pallet);
        // 1. é”å®šæ‰˜ç›˜ï¼Œé¿å…ä¸Žè‡ªåŠ¨å½’é›†å¹¶å‘ä¿®æ”¹è¢‹æ•°
        MesProPalletDO pallet = palletMapper.selectByIdForUpdate(bindReqVO.getPalletId());
        if (pallet == null) {
            throw exception(PRO_PALLET_NOT_EXISTS);
        }
        // 2. é”å®šå¹¶æ ¡éªŒè¢‹ç 
        List<MesProBagCodeDO> bagCodes = validateBagCodesForBind(bindReqVO.getIds(), pallet, true);
        // 3. æ ¡éªŒä¸è¶…过托盘可装袋数
        int currentCount = pallet.getBagCount() == null ? 0 : pallet.getBagCount();
        if (pallet.getCapacity() != null && pallet.getCapacity() > 0
@@ -260,10 +266,13 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void unbindPallet(MesProBagCodeBindReqVO bindReqVO) {
        // 1. æ ¡éªŒæ‰˜ç›˜å­˜åœ¨
        MesProPalletDO pallet = palletService.validatePalletExists(bindReqVO.getPalletId());
        // 2. æ ¡éªŒè¢‹ç å‡ç»‘定在当前托盘上
        List<MesProBagCodeDO> bagCodes = bagCodeMapper.selectListByIds(bindReqVO.getIds());
        // 1. é”å®šæ‰˜ç›˜ï¼Œé¿å…ä¸Žè‡ªåŠ¨å½’é›†å¹¶å‘ä¿®æ”¹è¢‹æ•°
        MesProPalletDO pallet = palletMapper.selectByIdForUpdate(bindReqVO.getPalletId());
        if (pallet == null) {
            throw exception(PRO_PALLET_NOT_EXISTS);
        }
        // 2. é”å®šå¹¶æ ¡éªŒè¢‹ç å‡ç»‘定在当前托盘上
        List<MesProBagCodeDO> bagCodes = bagCodeMapper.selectListByIdsForUpdate(bindReqVO.getIds());
        if (bagCodes.size() != bindReqVO.getIds().size()) {
            throw exception(PRO_BAG_CODE_NOT_EXISTS);
        }
@@ -339,8 +348,8 @@
    /**
     * æ ¡éªŒå¾…绑定的袋码:存在、与托盘同批次、未绑定过托盘、状态有效(已生成/已导出印刷)
     */
    private List<MesProBagCodeDO> validateBagCodesForBind(List<Long> ids, MesProPalletDO pallet) {
        List<MesProBagCodeDO> bagCodes = bagCodeMapper.selectListByIds(ids);
    private List<MesProBagCodeDO> validateBagCodesForBind(List<Long> ids, MesProPalletDO pallet, boolean lock) {
        List<MesProBagCodeDO> bagCodes = lock ? bagCodeMapper.selectListByIdsForUpdate(ids) : bagCodeMapper.selectListByIds(ids);
        if (bagCodes.size() != ids.size()) {
            throw exception(PRO_BAG_CODE_NOT_EXISTS);
        }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/batch/MesProBatchServiceImpl.java
@@ -15,6 +15,7 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchWorkerMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
@@ -67,6 +68,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createBatch(MesProBatchSaveReqVO createReqVO) {
        validateBagType(createReqVO.getBagType());
        // 1. æ ¡éªŒå…³è”数据(品种、产线、生产人员)
        MesMdmItemDO item = mdmItemService.validateItemExists(createReqVO.getItemId());
        MesProLineDO line = lineService.validateLineExistsAndEnable(createReqVO.getLineId());
@@ -80,6 +82,7 @@
                .setCode(code)
                .setLineCode(line.getCode())
                .setLineName(line.getName())
                .setBagType(createReqVO.getBagType())
                .setBagQuantity(0)
                .setPalletQuantity(0)
                .setStatus(MesProBatchStatusEnum.DRAFT.getStatus());
@@ -96,6 +99,12 @@
        // 1. æ ¡éªŒå­˜åœ¨ + çŠ¶æ€ï¼ˆè‰ç¨¿/生产中才允许修改)
        MesProBatchDO oldBatch = validateBatchExists(updateReqVO.getId());
        validateBatchEditable(oldBatch);
        // 2. å·²ç”Ÿæˆè¢‹ç æˆ–托盘码后不允许修改袋型(避免同一批次阈值变化导致归集错乱)
        if (updateReqVO.getBagType() != null && !Objects.equals(oldBatch.getBagType(), updateReqVO.getBagType())
                && (bagQuantityOrPalletCreated(oldBatch))) {
            throw exception(PRO_BATCH_BAG_TYPE_NOT_ALLOW_CHANGE);
        }
        validateBagType(updateReqVO.getBagType());
        // 2. æ ¡éªŒå…³è”数据(生产人员)
        validateRelations(updateReqVO);
@@ -157,6 +166,9 @@
        }
        if (!valid) {
            throw exception(PRO_BATCH_STATUS_INVALID);
        }
        if (MesProBatchStatusEnum.PRODUCING.getStatus().equals(status)) {
            validateBagType(batch.getBagType());
        }
        // 3. æ›´æ–°çŠ¶æ€
        batchMapper.updateById(new MesProBatchDO().setId(id).setStatus(status));
@@ -246,6 +258,23 @@
    }
    /**
     * æ ¡éªŒåŒ…装袋型有效
     */
    private void validateBagType(Integer bagType) {
        if (bagType != null && MesProBagTypeEnum.valueOfType(bagType) == null) {
            throw exception(PRO_BATCH_BAG_TYPE_INVALID);
        }
    }
    /**
     * åˆ¤æ–­æ‰¹æ¬¡æ˜¯å¦å·²ç”Ÿæˆè¢‹ç æˆ–托盘码(用于禁止修改袋型)
     */
    private boolean bagQuantityOrPalletCreated(MesProBatchDO batch) {
        return (batch.getBagQuantity() != null && batch.getBagQuantity() > 0)
                || (batch.getPalletQuantity() != null && batch.getPalletQuantity() > 0);
    }
    /**
     * æ’入批次生产人员配置
     */
    private void insertBatchWorkers(Long batchId, List<MesProBatchSaveReqVO.Worker> workers) {
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletService.java
@@ -1,6 +1,7 @@
package cn.iocoder.yudao.module.mes.service.pro.pallet;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletAutoPrintRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletBatchCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.pallet.MesProPalletDO;
@@ -24,6 +25,14 @@
    List<Long> batchCreatePallet(@Valid MesProPalletBatchCreateReqVO createReqVO);
    /**
     * æŒ‰æ‰¹æ¬¡æœ‰æ•ˆè¢‹ç æ•°é‡è‡ªåŠ¨ç”Ÿæˆå¹¶ç»‘å®šæ•´æ‰˜è¢‹ç 
     *
     * @param batchId æ‰¹æ¬¡ç¼–号
     * @return æœ¬æ¬¡æ–°ç”Ÿæˆçš„æ‰˜ç›˜æ‰“印数据
     */
    List<MesProPalletAutoPrintRespVO> autoCreatePallets(Long batchId);
    /**
     * åˆ é™¤æ‰˜ç›˜ç ï¼ˆæœªç»‘定袋码才允许删除)
     *
     * @param id ç¼–号
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/pallet/MesProPalletServiceImpl.java
@@ -2,6 +2,7 @@
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletAutoPrintRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletBatchCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.pallet.vo.MesProPalletPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
@@ -9,8 +10,13 @@
import cn.iocoder.yudao.module.mes.dal.mysql.pro.bagcode.MesProBagCodeMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.batch.MesProBatchMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.pro.pallet.MesProPalletMapper;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBagTypeEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProBatchStatusEnum;
import cn.iocoder.yudao.module.mes.enums.pro.MesProPalletStatusEnum;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
@@ -18,6 +24,7 @@
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -39,6 +46,10 @@
    private MesProBagCodeMapper bagCodeMapper;
    @Resource
    private MesProBatchMapper batchMapper;
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
    private AdminUserApi adminUserApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -69,6 +80,7 @@
                    .setPalletNo(palletNo)
                    .setCapacity(createReqVO.getCapacity())
                    .setBagCount(0)
                    .setAutoGenerated(false)
                    .setStatus(MesProPalletStatusEnum.IN_USE.getStatus())
                    .setRemark(createReqVO.getRemark()));
        }
@@ -79,6 +91,57 @@
                .setPalletQuantity((batch.getPalletQuantity() == null ? 0 : batch.getPalletQuantity())
                        + createReqVO.getCount()));
        return pallets.stream().map(MesProPalletDO::getId).toList();
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<MesProPalletAutoPrintRespVO> autoCreatePallets(Long batchId) {
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(batchId);
        if (batch == null) {
            throw exception(PRO_BATCH_NOT_EXISTS);
        }
        if (!MesProBatchStatusEnum.PRODUCING.getStatus().equals(batch.getStatus())) {
            throw exception(PRO_BATCH_STATUS_NOT_GENERATE_CODE);
        }
        MesProBagTypeEnum bagType = MesProBagTypeEnum.valueOfType(batch.getBagType());
        if (bagType == null) {
            throw exception(PRO_BATCH_BAG_TYPE_INVALID);
        }
        List<cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO> candidates =
                bagCodeMapper.selectUnboundEffectiveListByBatchId(batchId);
        int threshold = bagType.getThreshold();
        int palletCount = candidates.size() / threshold;
        if (palletCount == 0) {
            return Collections.emptyList();
        }
        int startNo = palletMapper.selectMaxPalletNo(batchId) + 1;
        List<MesProPalletDO> pallets = new ArrayList<>(palletCount);
        List<List<Long>> bagIdGroups = new ArrayList<>(palletCount);
        for (int i = 0; i < palletCount; i++) {
            int palletNo = startNo + i;
            MesProPalletDO pallet = new MesProPalletDO()
                    .setCode(batch.getCode() + "-P" + StrUtil.padPre(String.valueOf(palletNo), 2, '0'))
                    .setBatchId(batchId)
                    .setBatchCode(batch.getCode())
                    .setPalletNo(palletNo)
                    .setCapacity(threshold)
                    .setBagCount(threshold)
                    .setAutoGenerated(true)
                    .setStatus(MesProPalletStatusEnum.IN_USE.getStatus())
                    .setRemark("按有效袋码阈值自动生成");
            pallets.add(pallet);
            bagIdGroups.add(candidates.subList(i * threshold, (i + 1) * threshold).stream()
                    .map(cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO::getId).toList());
        }
        palletMapper.insertBatch(pallets);
        for (int i = 0; i < pallets.size(); i++) {
            MesProPalletDO pallet = pallets.get(i);
            bagCodeMapper.bindPalletByIds(bagIdGroups.get(i), pallet.getId(), pallet.getCode());
        }
        batchMapper.updateById(new MesProBatchDO().setId(batchId)
                .setPalletQuantity((batch.getPalletQuantity() == null ? 0 : batch.getPalletQuantity()) + pallets.size()));
        return buildAutoPrintVOs(pallets, batch);
    }
    @Override
@@ -100,6 +163,30 @@
        }
    }
    private List<MesProPalletAutoPrintRespVO> buildAutoPrintVOs(List<MesProPalletDO> pallets, MesProBatchDO batch) {
        MesMdmItemDO item = batch.getItemId() == null ? null : mdmItemService.getItem(batch.getItemId());
        AdminUserRespDTO owner = batch.getOwnerUserId() == null ? null : adminUserApi.getUser(batch.getOwnerUserId());
        return pallets.stream().map(pallet -> new MesProPalletAutoPrintRespVO()
                .setId(pallet.getId())
                .setCode(pallet.getCode())
                .setBatchId(pallet.getBatchId())
                .setBatchCode(pallet.getBatchCode())
                .setPalletNo(pallet.getPalletNo())
                .setCopies(3)
                .setCapacity(pallet.getCapacity())
                .setBagCount(pallet.getBagCount())
                .setItemId(batch.getItemId())
                .setItemCode(item == null ? null : item.getCode())
                .setItemName(item == null ? null : item.getName())
                .setItemSpecification(item == null ? null : item.getSpecification())
                .setLineCode(batch.getLineCode())
                .setLineName(batch.getLineName())
                .setProduceDate(batch.getProduceDate())
                .setOwnerUserId(batch.getOwnerUserId())
                .setOwnerUserName(owner == null ? null : owner.getNickname()))
                .toList();
    }
    @Override
    public MesProPalletDO getPallet(Long id) {
        return palletMapper.selectById(id);