已添加29个文件
已修改17个文件
1295 ■■■■■ 文件已修改
docs/mes_pro_pallet_auto_generation_integration.md 144 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_scan_event_integration.md 103 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/mes_seed_quality_integration.md 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mes_scan_event_inbound.sql 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_pro_pallet_auto_threshold.sql 44 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_pro_scan_inbound_init.sql 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/mysql/mes_seed_quality_init.sql 25 ●●●●● 补丁 | 查看 | 原始文档 | 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/controller/admin/pro/scan/MesProScanEventController.java 41 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventCreateReqVO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventPageReqVO.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventRespVO.java 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/MesQcSeedQualityController.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualitySubmitReqVO.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java 62 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/batch/MesProBatchDO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | 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/dataobject/pro/scan/MesProScanEventDO.java 36 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityDO.java 20 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityIndicatorDO.java 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualitySourceDO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/bagcode/MesProBagCodeMapper.java 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/batch/MesProBatchMapper.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java 32 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java 5 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java 13 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualitySourceMapper.java 11 ●●●●● 补丁 | 查看 | 原始文档 | 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 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesProScanEventStatusEnum.java 6 ●●●●● 补丁 | 查看 | 原始文档 | 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 | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java 42 ●●●●● 补丁 | 查看 | 原始文档 | 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` ä¸ºå‡†ã€‚
docs/mes_scan_event_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,103 @@
# ç”Ÿäº§çº¿æ‰«ç å…¥åº“ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§çº¿æ‰«ç å…¥åº“自动工作台
- æ‰«ç äº‹ä»¶å®¡è®¡
- ç”Ÿäº§æ‰¹æ¬¡å’Œè¢‹ç è¯¦æƒ…
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. å·¥ä¸šç›¸æœºç”±è¾¹ç¼˜ç¨‹åºå®ŒæˆäºŒç»´ç è¯†åˆ«ï¼Œä¸Šä¼  `eventId`、设备编码、产线编码和袋码。
2. åŽç«¯æ ¹æ®è¢‹ç å¸¦å‡ºç”Ÿäº§æ‰¹æ¬¡å’Œäº§å“ç‰©æ–™ï¼Œå¹¶ä½¿ç”¨ WIP è™šæ‹Ÿä»“配置完成库存入账。
3. å…¥åº“成功后记录服务端入库时间,增加暂存库存,并累计批次入库袋数。
4. åŒä¸€ä¸ª `eventId` é‡è¯•时返回原事件;已入库袋码不会重复增加库存。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| POST | `/mes/pro/scan-in/inbound` | è¢‹ç æ‰«ç å…¥åº“,成功后增加 WIP è™šæ‹Ÿä»“库存并写库存事务 |
| GET | `/mes/pro/scan-in/page` | åˆ†é¡µæŸ¥è¯¢æ‰«ç äº‹ä»¶ |
| GET | `/mes/pro/scan-in/latest` | æŸ¥è¯¢è®¾å¤‡æˆ–全局最近扫码事件 |
| GET | `/mes/pro/scan-in/dashboard` | æŸ¥è¯¢å·¥ä½œå°æœ€è¿‘事件和累计事件数 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|---|---|---|---|
| eventId | String | æ˜¯ | è¾¹ç¼˜è®¾å¤‡ç”Ÿæˆçš„幂等事件号 |
| bagCode | String | æ˜¯ | å·¥ä¸šç›¸æœºè¯†åˆ«å‡ºçš„袋码 |
| quantity | Decimal | æ˜¯ | å…¥åº“数量,本期一袋通常为 1 |
| deviceCode | String | æ˜¯ | è®¾å¤‡ç¼–码,用于审计 |
| lineCode | String | å¦ | äº§çº¿ç¼–码,用于审计 |
| locationId | Long | æ˜¯ | WIP æš‚存库区编号 |
| areaId | Long | æ˜¯ | WIP æš‚存库位编号 |
| remark | String | å¦ | å¤‡æ³¨ |
**响应字段:**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|---|---|---|
| eventId | String | å¹‚等事件号 |
| bagCode | String | è¢‹ç  |
| batchCode | String | ç”Ÿäº§æ‰¹æ¬¡å· |
| quantity | Decimal | æœ¬æ¬¡å…¥åº“数量 |
| deviceCode | String | è®¾å¤‡ç¼–码 |
| eventTime | DateTime | æœåŠ¡ç«¯å…¥åº“æ—¶é—´ |
| transactionId | Long | åº“存事务编号 |
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|---|---|---|
| deviceCode | è®¾å¤‡çŠ¶æ€ã€æœ€è¿‘æ‰«ç åˆ—è¡¨ | æ˜¾ç¤ºå½“前设备或事件来源 |
| bagCode | æœ€è¿‘扫码列表 | æ˜¾ç¤ºæœ€è¿‘识别的袋码 |
| batchCode | æœ€è¿‘扫码列表、批次汇总 | å±•示袋码归属的生产批次 |
| eventTime | æœ€è¿‘扫码列表 | ä»¥æœåŠ¡ç«¯å…¥åº“æ—¶é—´ä¸ºå‡† |
| transactionId | æœ€è¿‘扫码列表 | ç”¨äºŽåº“存流水追溯 |
| quantity | åº“存摘要 | ç´¯è®¡æš‚存数量 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|---|---|
| é‡å¤äº‹ä»¶ | åŒä¸€ `eventId` è¿”回第一次处理结果,不重复增加库存 |
| å·²å…¥åº“袋码 | æ‹’绝重复入库,并保留业务错误信息 |
| WIP æš‚存库 | ä»“库由后端使用 WIP è™šæ‹Ÿä»“解析,前端不传仓库编号 |
| äº‹åŠ¡ä¸€è‡´æ€§ | äº‹ä»¶ã€è¢‹ç çŠ¶æ€ã€æ‰¹æ¬¡ç´¯è®¡ã€åº“å­˜å°è´¦å’Œåº“å­˜äº‹åŠ¡åœ¨åŒä¸€äº‹åŠ¡ä¸­æäº¤ |
| è®¾å¤‡æ—¶é—´ | è®¾å¤‡æ‰«ç æ—¶é—´ç”¨äºŽè¿½æº¯ï¼Œåº“存入库时间使用服务端时间 |
## å¤±è´¥çŠ¶æ€ä¸Žæ ‡å‡†é”™è¯¯ç 
扫码请求无论成功或失败都会保留扫码事件记录。状态取值:
| çŠ¶æ€ | è¯´æ˜Ž |
|---|---|
| SUCCESS | å·²å®Œæˆåº“存入库 |
| FAILED | æ ¡éªŒæˆ–库存处理失败 |
| é”™è¯¯ç  | è¯´æ˜Ž |
|---|---|
| MES_SCAN_BAG_NOT_EXISTS | è¢‹ç ä¸å­˜åœ¨ |
| MES_SCAN_BAG_ALREADY_INBOUND | è¢‹ç å·²å…¥åº“ |
| MES_SCAN_BATCH_NOT_EXISTS | ç”Ÿäº§æ‰¹æ¬¡ä¸å­˜åœ¨ |
| MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS | WIP è™šæ‹Ÿä»“不存在 |
| MES_SCAN_TRANSACTION_FAILED | åº“存事务处理失败 |
| MES_SCAN_EVENT_ID_CONFLICT | äº‹ä»¶å·å·²å­˜åœ¨ä½†å†…容不一致 |
失败记录保留设备编码、袋码、接收时间、失败时间、错误码和错误原因;失败时不产生库存事务。
## æƒé™ç‚¹
| æƒé™ | ç”¨é€” |
|---|---|
| mes:pro-scan-in:query | æŸ¥è¯¢äº‹ä»¶å’Œæœ€è¿‘扫码 |
| mes:pro-scan-in:inbound | æ‰§è¡Œæ‰«ç å…¥åº“ |
| mes:pro-scan-in:dashboard | æŸ¥è¯¢å·¥ä½œå°çœ‹æ¿ |
- è¾¹ç¼˜ç¨‹åºä¸å¾—上传图片或视频,本接口只接收解码后的二维码结果。
- å‰ç«¯å·¥ä½œå°åº”在页面卸载时停止刷新任务。
- å…¥åº“失败时展示后端返回的业务错误,不自动调用通用库存调整接口重试。
- WIP åº“区和库位需提前配置;接口不接受客户端指定仓库编号。
docs/mes_seed_quality_integration.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
# ç§å­è´¨æ£€å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç§å­è´¨æ£€æäº¤é¡µ
- ç§å­è´¨æ£€è®°å½•页
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. ç”Ÿäº§è¢‹ç æˆ–待检库存带入袋码、物料、批次信息。
2. æäº¤æ—¶ä»…传袋码、物料、批次和四项检测结果,仓库位置由后台配置决定。
3. åˆæ ¼æ—¶ç”±åŽç«¯å°†WIP/待检库移入正式库存;不合格时删除袋码和对应库存台账,但保留质检审计数据。
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|---|---|---|
| POST | `/mes/qc/seed-quality/submit` | æäº¤ç§å­è´¨æ£€ |
请求参数包括 `code`、`itemId`、`batchCode`、`reason` å’Œ `indicators`。`indicators` å¿…须恰好包含 `purity`、`cleanliness`、`germination_rate`、`moisture`,每项包含 `value`、`minValue`、`maxValue`,数值不得为负且结果必须在边界内。
成功响应为质检主表编号。重复袋码提交会被拒绝。
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|---|---|---|
| code | åˆ—表、详情 | è¢‹ç  |
| batchCode | åˆ—表、详情 | ç”Ÿäº§æ‰¹æ¬¡ |
| å››é¡¹æŒ‡æ ‡ | è¯¦æƒ… | å±•示实测值和合格上下限 |
| result | åˆ—表、详情 | åˆæ ¼æˆ–不合格 |
| reason | è¯¦æƒ… | ä¸åˆæ ¼åŽŸå›  |
| inspectorId、inspectedAt | è¯¦æƒ… | è´¨æ£€å‘˜å’Œè´¨æ£€æ—¶é—´ |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|---|---|
| æŒ‡æ ‡æ ¡éªŒ | å››é¡¹æŒ‡æ ‡å¿…须齐全、不可重复,实测值必须在边界内 |
| åˆæ ¼ | ç”ŸæˆMOVE_OUT和MOVE_IN,来源为WIP/待检库,目标为正式库存 |
| ä¸åˆæ ¼ | åˆ é™¤è¢‹ç å’Œå¯¹åº”库存台账,不调整生产批次数量,保留主表、指标、原因和事务审计 |
| å¹‚ç­‰ | è¢‹ç å·²å­˜åœ¨è´¨æ£€è®°å½•时拒绝再次提交 |
| æƒé™ | éœ€è¦ `mes:qc-seed-quality:submit` æƒé™ |
## æ³¨æ„äº‹é¡¹
- å‰ç«¯ä¸å¾—传仓库、库区、库位参数,后台配置编码为后续配置服务预留。
- ä¸åˆæ ¼ç»“果仍需展示,不能按业务删除质检主表及指标记录。
sql/mes_scan_event_inbound.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
ALTER TABLE mes_pro_bag_code ADD COLUMN inbound_time datetime NULL COMMENT '入库时间';
ALTER TABLE mes_pro_bag_code ADD COLUMN inbound_event_id bigint NULL COMMENT '入库事件ID';
ALTER TABLE mes_pro_bag_code ADD COLUMN inbound_device_code varchar(64) NULL COMMENT '入库设备编码';
ALTER TABLE mes_pro_bag_code ADD COLUMN inbound_line_code varchar(64) NULL COMMENT '入库产线编码';
ALTER TABLE mes_pro_batch ADD COLUMN inbound_bag_quantity int NULL DEFAULT 0 COMMENT '累计入库袋数' AFTER pallet_quantity;
ALTER TABLE mes_pro_batch ADD COLUMN inbound_event_id bigint NULL COMMENT '入库事件ID';
ALTER TABLE mes_pro_batch ADD COLUMN inbound_device_code varchar(64) NULL COMMENT '入库设备编码';
ALTER TABLE mes_pro_batch ADD COLUMN inbound_line_code varchar(64) NULL COMMENT '入库产线编码';
CREATE TABLE mes_pro_scan_event (
  id bigint NOT NULL AUTO_INCREMENT,
  event_id varchar(64) NOT NULL,
  bag_code varchar(128) NOT NULL,
  bag_code_id bigint NULL,
  batch_id bigint NULL,
  batch_code varchar(128) NULL,
  item_id bigint NULL,
  quantity decimal(18,6) NULL,
  device_code varchar(64) NOT NULL,
  line_code varchar(64) NULL,
  warehouse_id bigint NULL,
  location_id bigint NULL,
  area_id bigint NULL,
  transaction_id bigint NULL,
  status varchar(16) NOT NULL DEFAULT 'FAILED',
  error_code varchar(64) NULL,
  error_message varchar(500) NULL,
  received_time datetime NOT NULL,
  failed_time datetime NULL,
  event_time datetime NULL,
  remark varchar(500) NULL,
  creator varchar(64) NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
  updater varchar(64) NULL, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  deleted bit NOT NULL DEFAULT 0,
  PRIMARY KEY (id), UNIQUE KEY uk_mes_scan_event_event_id (event_id), UNIQUE KEY uk_mes_scan_event_bag (bag_code_id), KEY idx_mes_scan_event_status (status), KEY idx_mes_scan_event_error_code (error_code), KEY idx_mes_scan_event_received_time (received_time)
) ENGINE=InnoDB COMMENT='MES袋码扫码入库事件';
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');
sql/mysql/mes_pro_scan_inbound_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
-- ç”Ÿäº§çº¿æ‰«ç å…¥åº“菜单与按钮权限
-- èœå•挂载到一袋一码管理(602630),组件路径与前端动态路由一致。
UPDATE system_menu
SET parent_id = 602630, sort = 7, updater = '1', update_time = NOW()
WHERE id = 602650 AND deleted = 0;
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
                         status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT 602650, '生产线扫码入库', '', 2, 7, 602630, 'scan-in', 'ep:camera', 'mes/pro/scan-in/index', 'MesProScanIn',
       0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 602650 AND deleted = 0);
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
                         status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT 602651, '扫码入库查询', 'mes:pro-scan-in:query', 3, 1, 602650, '', '', '', '',
       0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 602651 AND deleted = 0);
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
                         status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT 602652, '扫码入库', 'mes:pro-scan-in:inbound', 3, 2, 602650, '', '', '', '',
       0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 602652 AND deleted = 0);
INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component, component_name,
                         status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT 602653, '扫码入库看板', 'mes:pro-scan-in:dashboard', 3, 3, 602650, '', '', '', '',
       0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE id = 602653 AND deleted = 0);
sql/mysql/mes_seed_quality_init.sql
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,25 @@
-- ç§å­è´¨æ£€åˆå§‹åŒ–脚本(无租户字段,可重复执行)
CREATE TABLE IF NOT EXISTS mes_qc_seed_quality (
  id bigint NOT NULL AUTO_INCREMENT COMMENT '编号', code varchar(64) NOT NULL COMMENT '袋码', item_id bigint NOT NULL COMMENT '物料', batch_code varchar(64) DEFAULT NULL COMMENT '批次号', result tinyint NOT NULL DEFAULT 0 COMMENT '0处理中1合格2不合格', reason varchar(500) DEFAULT NULL COMMENT '原因', inspector_id bigint DEFAULT NULL COMMENT '质检员', inspected_at datetime DEFAULT NULL COMMENT '质检时间', creator varchar(64) DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updater varchar(64) DEFAULT NULL, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted bit NOT NULL DEFAULT 0, PRIMARY KEY (id), UNIQUE KEY uk_code (code)
) ENGINE=InnoDB COMMENT='种子质检主表';
CREATE TABLE IF NOT EXISTS mes_qc_seed_quality_indicator (
  id bigint NOT NULL AUTO_INCREMENT, quality_id bigint NOT NULL, indicator_code varchar(32) NOT NULL, value decimal(18,6) NOT NULL, min_value decimal(18,6) NOT NULL, max_value decimal(18,6) NOT NULL, creator varchar(64) DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updater varchar(64) DEFAULT NULL, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted bit NOT NULL DEFAULT 0, PRIMARY KEY(id), UNIQUE KEY uk_quality_indicator(quality_id,indicator_code)
) ENGINE=InnoDB COMMENT='种子质检指标结果';
CREATE TABLE IF NOT EXISTS mes_qc_seed_quality_source (
  id bigint NOT NULL AUTO_INCREMENT, quality_id bigint NOT NULL, bag_code_id bigint DEFAULT NULL, bag_code varchar(64) NOT NULL, material_stock_id bigint DEFAULT NULL, quantity decimal(18,6) NOT NULL DEFAULT 0, source_warehouse_id bigint DEFAULT NULL, source_location_id bigint DEFAULT NULL, source_area_id bigint DEFAULT NULL, target_warehouse_id bigint DEFAULT NULL, target_location_id bigint DEFAULT NULL, target_area_id bigint DEFAULT NULL, creator varchar(64) DEFAULT NULL, create_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, updater varchar(64) DEFAULT NULL, update_time datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, deleted bit NOT NULL DEFAULT 0, PRIMARY KEY(id), UNIQUE KEY uk_quality_bag(quality_id,bag_code)
) ENGINE=InnoDB COMMENT='种子质检库存袋码来源明细';
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, component, component_name, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT '种子质检', '', 2, 90, 5500, 'seed-quality', 'ep:checked', 'mes/qc/seedQuality/index', 'MesQcSeedQuality', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM DUAL
WHERE NOT EXISTS (SELECT 1 FROM system_menu WHERE parent_id = 5500 AND path = 'seed-quality' AND deleted = 0);
INSERT INTO system_menu (name, permission, type, sort, parent_id, path, icon, status, visible, keep_alive, always_show, creator, create_time, updater, update_time, deleted)
SELECT '提交质检', 'mes:qc-seed-quality:submit', 3, 1, id, '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
FROM system_menu
WHERE parent_id = 5500 AND path = 'seed-quality' AND deleted = 0
  AND NOT EXISTS (
    SELECT 1 FROM system_menu child
    WHERE child.parent_id = system_menu.id
      AND child.permission = 'mes:qc-seed-quality:submit'
      AND child.deleted = 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/controller/admin/pro/scan/MesProScanEventController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.scan;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventRespVO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
import java.util.List;
import java.util.Map;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
import cn.iocoder.yudao.module.mes.service.pro.scan.MesProScanEventService;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.*;
import org.springframework.security.access.prepost.PreAuthorize;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@Tag(name = "管理后台 - MES æ‰«ç å…¥åº“")
@RestController @RequestMapping("/mes/pro/scan-in")
public class MesProScanEventController {
    @Resource private MesProScanEventService service;
    @PostMapping("/inbound") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:inbound')") @Operation(summary = "袋码扫码入库")
    public CommonResult<MesProScanEventRespVO> inbound(@Valid @RequestBody MesProScanEventCreateReqVO req) {
        return success(BeanUtils.toBean(service.createInbound(req), MesProScanEventRespVO.class));
    }
    @GetMapping("/page") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:query')") @Operation(summary = "扫码事件分页")
    public CommonResult<PageResult<MesProScanEventRespVO>> page(@Valid MesProScanEventPageReqVO req) {
        PageResult<MesProScanEventDO> page = service.getPage(req);
        return success(new PageResult<>(BeanUtils.toBean(page.getList(), MesProScanEventRespVO.class), page.getTotal()));
    }
    @GetMapping("/latest") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:query')") @Operation(summary = "最新扫码事件")
    public CommonResult<MesProScanEventRespVO> latest(@RequestParam(required = false) String deviceCode) {
        return success(BeanUtils.toBean(service.getLatest(deviceCode), MesProScanEventRespVO.class));
    }
    @GetMapping("/dashboard") @PreAuthorize("@ss.hasPermission('mes:pro-scan-in:dashboard')") @Operation(summary = "扫码入库看板")
    public CommonResult<Map<String, Object>> dashboard() { return success(service.getDashboard()); }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventCreateReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.math.BigDecimal;
@Data
public class MesProScanEventCreateReqVO {
    @NotBlank private String eventId;
    @NotBlank private String bagCode;
    @NotNull @DecimalMin("0.000001") private BigDecimal quantity;
    @NotBlank private String deviceCode;
    private String lineCode;
    @NotNull private Long locationId;
    @NotNull private Long areaId;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventPageReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class MesProScanEventPageReqVO extends PageParam {
    private String eventId;
    private String bagCode;
    private String batchCode;
    private String deviceCode;
    private String status;
    private String errorCode;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/scan/vo/MesProScanEventRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Data
public class MesProScanEventRespVO {
    private Long id;
    private String eventId;
    private String bagCode;
    private String batchCode;
    private BigDecimal quantity;
    private String deviceCode;
    private String lineCode;
    private Long warehouseId;
    private Long locationId;
    private Long areaId;
    private Long transactionId;
    private String status;
    private String errorCode;
    private String errorMessage;
    private LocalDateTime receivedTime;
    private LocalDateTime failedTime;
    private LocalDateTime eventTime;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/MesQcSeedQualityController.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
import cn.iocoder.yudao.module.mes.service.qc.seedquality.MesQcSeedQualityService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - MES ç§å­è´¨æ£€")
@RestController @RequestMapping("/mes/qc/seed-quality") @Validated
public class MesQcSeedQualityController {
    @Resource private MesQcSeedQualityService service;
    @PostMapping("/submit") @Operation(summary = "提交种子质检")
    @PreAuthorize("@ss.hasPermission('mes:qc-seed-quality:submit')")
    public CommonResult<Long> submit(@Valid @RequestBody MesQcSeedQualitySubmitReqVO reqVO) { return success(service.submit(reqVO)); }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/qc/seedquality/vo/MesQcSeedQualitySubmitReqVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo;
import jakarta.validation.Valid;
import jakarta.validation.constraints.*;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class MesQcSeedQualitySubmitReqVO {
    @NotBlank private String code;
    @NotNull private Long itemId;
    private String batchCode;
    @NotEmpty @Valid private List<Indicator> indicators;
    private String reason;
    @Data public static class Indicator {
        @NotBlank private String code;
        @NotNull @DecimalMin("0") private BigDecimal value;
        @NotNull @DecimalMin("0") private BigDecimal minValue;
        @NotNull @DecimalMin("0") private BigDecimal maxValue;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/bagcode/MesProBagCodeDO.java
@@ -10,16 +10,6 @@
import java.time.LocalDateTime;
import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
/**
 * MES è¢‹ç èµ‹ç ï¼ˆä¸€è¢‹ä¸€ç ï¼‰DO
 * <p>
 * æ ¹æ®æ‰¹æ¬¡ä¿¡æ¯è‡ªåŠ¨ç”Ÿæˆå”¯ä¸€äºŒç»´ç æ•°æ®åŒ…ï¼šç å€¼ = æ‰¹æ¬¡å· + - + è¢‹åºå·ï¼Œå…¨å±€å”¯ä¸€ï¼Œæ»¡è¶³å”¯ä¸€æ€§ã€å¯è¯†åˆ«æ€§ã€å¯è¿½æº¯æ€§è¦æ±‚。
 * æ”¯æŒæ‰¹é‡å¯¼å‡ºäº¤ä»˜å°åˆ·ï¼Œå¹¶ä¸Žæ‰˜ç›˜ç å…³è”。
 *
 * @author è¶…级管理员
 */
@TableName("mes_pro_bag_code")
@KeySequence("mes_pro_bag_code_seq")
@Data
@@ -29,66 +19,22 @@
@NoArgsConstructor
@AllArgsConstructor
public class MesProBagCodeDO extends BaseDO {
    /**
     * ç¼–号
     */
    @TableId
    private Long id;
    /**
     * ç”Ÿæˆç»„ UUID(标记同一次生成的数据包)
     */
    private String uuid;
    /**
     * è¢‹ç ï¼ˆäºŒç»´ç å†…容:批次号-袋序号,全局唯一)
     */
    private String code;
    /**
     * æ‰¹æ¬¡å†…袋序号
     */
    private Integer bagNo;
    /**
     * ç”Ÿäº§æ‰¹æ¬¡ç¼–号
     * <p>
     * å…³è” {@link MesProBatchDO#getId()}
     */
    private Long batchId;
    /**
     * æ‰¹æ¬¡å·ï¼ˆå†—余,便于展示)
     */
    private String batchCode;
    /**
     * ç»‘定托盘编号
     * <p>
     * å…³è” {@link MesProPalletDO#getId()}
     */
    private Long palletId;
    /**
     * æ‰˜ç›˜ç ï¼ˆå†—余,便于展示)
     */
    private String palletCode;
    /**
     * çŠ¶æ€
     * <p>
     * å­—å…¸ {@link DictTypeConstants#MES_PRO_BAG_CODE_STATUS}
     * æžšä¸¾ {@link cn.iocoder.yudao.module.mes.enums.pro.MesProBagCodeStatusEnum}
     */
    private Integer status;
    /**
     * å¯¼å‡ºï¼ˆäº¤ä»˜å°åˆ·ï¼‰æ—¶é—´
     */
    private LocalDateTime printTime;
    /**
     * ä½œåºŸæ—¶é—´
     */
    private LocalDateTime cancelTime;
    /**
     * ä½œåºŸåŽŸå› 
     */
    private String cancelReason;
    /**
     * å¤‡æ³¨
     */
    private LocalDateTime inboundTime;
    private Long inboundEventId;
    private String inboundDeviceCode;
    private String inboundLineCode;
    private String remark;
}
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()}
@@ -80,6 +84,16 @@
     * å·²ç”Ÿæˆæ‰˜ç›˜ç æ•°é‡
     */
    private Integer palletQuantity;
    /** å…¥åº“æ—¶é—´ */
    private LocalDateTime inboundTime;
    /** ç´¯è®¡å…¥åº“袋数 */
    private Integer inboundBagQuantity;
    /** æœ€è¿‘入库事件编号 */
    private Long inboundEventId;
    /** å…¥åº“设备编码 */
    private String inboundDeviceCode;
    /** å…¥åº“产线编码 */
    private String inboundLineCode;
    /**
     * æ‰¹æ¬¡çŠ¶æ€
     * <p>
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/dataobject/pro/scan/MesProScanEventDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.KeySequence;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.*;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@TableName("mes_pro_scan_event")
@KeySequence("mes_pro_scan_event_seq")
@Data @EqualsAndHashCode(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor
public class MesProScanEventDO extends BaseDO {
    @TableId private Long id;
    private String eventId;
    private String bagCode;
    private Long bagCodeId;
    private Long batchId;
    private String batchCode;
    private Long itemId;
    private BigDecimal quantity;
    private String deviceCode;
    private String lineCode;
    private Long warehouseId;
    private Long locationId;
    private Long areaId;
    private Long transactionId;
    private String status;
    private String errorCode;
    private String errorMessage;
    private LocalDateTime receivedTime;
    private LocalDateTime failedTime;
    private LocalDateTime eventTime;
    private String remark;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import java.time.LocalDateTime;
@TableName("mes_qc_seed_quality")
@KeySequence("mes_qc_seed_quality_seq")
@Data @EqualsAndHashCode(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor
public class MesQcSeedQualityDO extends BaseDO {
    @TableId private Long id;
    private String code;
    private Long itemId;
    private String batchCode;
    private Integer result;
    private String reason;
    private Long inspectorId;
    private LocalDateTime inspectedAt;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualityIndicatorDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import java.math.BigDecimal;
@TableName("mes_qc_seed_quality_indicator")
@KeySequence("mes_qc_seed_quality_indicator_seq")
@Data @EqualsAndHashCode(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor
public class MesQcSeedQualityIndicatorDO extends BaseDO {
    @TableId private Long id;
    private Long qualityId;
    private String indicatorCode;
    private BigDecimal value;
    private BigDecimal minValue;
    private BigDecimal maxValue;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/qc/seedquality/MesQcSeedQualitySourceDO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
import com.baomidou.mybatisplus.annotation.*;
import lombok.*;
import java.math.BigDecimal;
@TableName("mes_qc_seed_quality_source")
@KeySequence("mes_qc_seed_quality_source_seq")
@Data @EqualsAndHashCode(callSuper = true) @Builder @NoArgsConstructor @AllArgsConstructor
public class MesQcSeedQualitySourceDO extends BaseDO {
    @TableId private Long id;
    private Long qualityId;
    private Long bagCodeId;
    private String bagCode;
    private Long materialStockId;
    private BigDecimal quantity;
    private Long sourceWarehouseId;
    private Long sourceLocationId;
    private Long sourceAreaId;
    private Long targetWarehouseId;
    private Long targetLocationId;
    private Long targetAreaId;
}
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;
@@ -27,6 +28,15 @@
        return selectOne(MesProBagCodeDO::getCode, code);
    }
    default MesProBagCodeDO selectByCodeForUpdate(String code) {
        return selectOne(new LambdaQueryWrapperX<MesProBagCodeDO>()
                .eq(MesProBagCodeDO::getCode, code).last("FOR UPDATE"));
    }
    @org.apache.ibatis.annotations.Update("UPDATE mes_pro_bag_code SET inbound_time = NOW(), inbound_event_id = #{eventId}, inbound_device_code = #{deviceCode}, inbound_line_code = #{lineCode}, update_time = NOW() WHERE id = #{id} AND deleted = 0 AND inbound_event_id IS NULL")
    int markInbound(@org.apache.ibatis.annotations.Param("id") Long id, @org.apache.ibatis.annotations.Param("eventId") Long eventId,
                    @org.apache.ibatis.annotations.Param("deviceCode") String deviceCode, @org.apache.ibatis.annotations.Param("lineCode") String lineCode);
    default List<MesProBagCodeDO> selectListByUuid(String uuid) {
        return selectList(MesProBagCodeDO::getUuid, uuid);
    }
@@ -39,6 +49,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/dal/mysql/pro/batch/MesProBatchMapper.java
@@ -27,6 +27,10 @@
    @Select("SELECT * FROM mes_pro_batch WHERE id = #{id} AND deleted = 0 FOR UPDATE")
    MesProBatchDO selectByIdForUpdate(@org.apache.ibatis.annotations.Param("id") Long id);
    @org.apache.ibatis.annotations.Update("UPDATE mes_pro_batch SET inbound_time = NOW(), inbound_event_id = #{eventId}, inbound_device_code = #{deviceCode}, inbound_line_code = #{lineCode}, inbound_bag_quantity = COALESCE(inbound_bag_quantity, 0) + 1, update_time = NOW() WHERE id = #{id} AND deleted = 0")
    int markInbound(@org.apache.ibatis.annotations.Param("id") Long id, @org.apache.ibatis.annotations.Param("eventId") Long eventId,
                    @org.apache.ibatis.annotations.Param("deviceCode") String deviceCode, @org.apache.ibatis.annotations.Param("lineCode") String lineCode);
    default PageResult<MesProBatchDO> selectPage(MesProBatchPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesProBatchDO>()
                .likeIfPresent(MesProBatchDO::getCode, reqVO.getCode())
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/scan/MesProScanEventMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,32 @@
package cn.iocoder.yudao.module.mes.dal.mysql.pro.scan;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MesProScanEventMapper extends BaseMapperX<MesProScanEventDO> {
    default MesProScanEventDO selectByEventIdForUpdate(String eventId) {
        return selectOne(new LambdaQueryWrapperX<MesProScanEventDO>()
                .eq(MesProScanEventDO::getEventId, eventId).last("FOR UPDATE"));
    }
    default PageResult<MesProScanEventDO> selectPage(MesProScanEventPageReqVO reqVO) {
        return selectPage(reqVO, new LambdaQueryWrapperX<MesProScanEventDO>()
                .eqIfPresent(MesProScanEventDO::getEventId, reqVO.getEventId())
                .likeIfPresent(MesProScanEventDO::getBagCode, reqVO.getBagCode())
                .likeIfPresent(MesProScanEventDO::getBatchCode, reqVO.getBatchCode())
                .eqIfPresent(MesProScanEventDO::getDeviceCode, reqVO.getDeviceCode())
                .eqIfPresent(MesProScanEventDO::getStatus, reqVO.getStatus())
                .eqIfPresent(MesProScanEventDO::getErrorCode, reqVO.getErrorCode())
                .orderByDesc(MesProScanEventDO::getEventTime));
    }
    default MesProScanEventDO selectLatest(String deviceCode) {
        return selectOne(new LambdaQueryWrapperX<MesProScanEventDO>()
                .eqIfPresent(MesProScanEventDO::getDeviceCode, deviceCode)
                .orderByDesc(MesProScanEventDO::getEventTime).last("LIMIT 1"));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityIndicatorMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,5 @@
package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityIndicatorDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper public interface MesQcSeedQualityIndicatorMapper extends BaseMapperX<MesQcSeedQualityIndicatorDO> {}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualityMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,13 @@
package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualityDO;
import org.apache.ibatis.annotations.Mapper;
@Mapper
public interface MesQcSeedQualityMapper extends BaseMapperX<MesQcSeedQualityDO> {
    default MesQcSeedQualityDO selectByCode(String code) {
        return selectOne(new LambdaQueryWrapperX<MesQcSeedQualityDO>().eq(MesQcSeedQualityDO::getCode, code));
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/qc/seedquality/MesQcSeedQualitySourceMapper.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,11 @@
package cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality;
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.MesQcSeedQualitySourceDO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
@Mapper public interface MesQcSeedQualitySourceMapper extends BaseMapperX<MesQcSeedQualitySourceDO> {
    @Update("UPDATE mes_qc_seed_quality_source SET deleted = 1, update_time = NOW() WHERE quality_id = #{qualityId} AND deleted = 0")
    int deleteByQualityId(@Param("qualityId") Long qualityId);
}
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
@@ -9,6 +9,16 @@
 */
public interface ErrorCodeConstants {
    // ========== MES ç”Ÿäº§ç®¡ç†-扫码入库(1-040-755-000) ==========
    ErrorCode PRO_SCAN_EVENT_BAG_NOT_EXISTS = new ErrorCode(1_040_755_000, "袋码不存在");
    ErrorCode PRO_SCAN_EVENT_ALREADY_INBOUND = new ErrorCode(1_040_755_001, "袋码已入库");
    ErrorCode PRO_SCAN_EVENT_BATCH_NOT_EXISTS = new ErrorCode(1_040_755_002, "生产批次不存在");
    ErrorCode PRO_SCAN_EVENT_WIP_WAREHOUSE_NOT_EXISTS = new ErrorCode(1_040_755_003, "WIP虚拟仓不存在");
    ErrorCode PRO_SCAN_EVENT_LOCATION_INVALID = new ErrorCode(1_040_755_004, "扫码入库库位无效");
    ErrorCode PRO_SCAN_EVENT_TRANSACTION_FAILED = new ErrorCode(1_040_755_005, "库存事务处理失败");
    ErrorCode PRO_SCAN_EVENT_ID_CONFLICT = new ErrorCode(1_040_755_006, "事件号已存在但内容不一致");
    ErrorCode PRO_SCAN_EVENT_FAILED = new ErrorCode(1_040_755_007, "扫码入库失败");
    // ========== MES åŸºç¡€æ•°æ®-物料分类(1-040-100-000) ==========
    ErrorCode MD_ITEM_TYPE_NOT_EXISTS = new ErrorCode(1_040_100_000, "物料分类不存在");
    ErrorCode MD_ITEM_TYPE_EXITS_CHILDREN = new ErrorCode(1_040_100_001, "存在子分类,无法删除");
@@ -809,6 +819,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/MesProScanEventStatusEnum.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,6 @@
package cn.iocoder.yudao.module.mes.enums;
public interface MesProScanEventStatusEnum {
    String SUCCESS = "SUCCESS";
    String FAILED = "FAILED";
}
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);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,17 @@
package cn.iocoder.yudao.module.mes.service.pro.scan;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
import jakarta.validation.Valid;
import java.util.Map;
public interface MesProScanEventService {
    MesProScanEventDO createInbound(@Valid MesProScanEventCreateReqVO reqVO);
    MesProScanEventDO getByEventId(String eventId);
    PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO);
    MesProScanEventDO getLatest(String deviceCode);
    Map<String, Object> getDashboard();
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/scan/MesProScanEventServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
package cn.iocoder.yudao.module.mes.service.pro.scan;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventCreateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pro.scan.vo.MesProScanEventPageReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.bagcode.MesProBagCodeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.batch.MesProBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.scan.MesProScanEventDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.warehouse.MesWmWarehouseDO;
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.scan.MesProScanEventMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.MesProScanEventStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
import cn.iocoder.yudao.module.mes.service.wm.warehouse.MesWmWarehouseService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
@Service
@Validated
public class MesProScanEventServiceImpl implements MesProScanEventService {
    @Resource private MesProScanEventMapper eventMapper;
    @Resource private MesProBagCodeMapper bagCodeMapper;
    @Resource private MesProBatchMapper batchMapper;
    @Resource private MesWmTransactionService transactionService;
    @Resource private MesWmWarehouseService warehouseService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public MesProScanEventDO createInbound(MesProScanEventCreateReqVO req) {
        MesProScanEventDO existing = eventMapper.selectByEventIdForUpdate(req.getEventId());
        if (existing != null) {
            if (!req.getBagCode().equals(existing.getBagCode()) || !req.getDeviceCode().equals(existing.getDeviceCode())) {
                return failed(existing, "MES_SCAN_EVENT_ID_CONFLICT", "事件号已存在但内容不一致");
            }
            return existing;
        }
        MesProScanEventDO event = new MesProScanEventDO();
        event.setEventId(req.getEventId()).setBagCode(req.getBagCode()).setQuantity(req.getQuantity())
                .setDeviceCode(req.getDeviceCode()).setLineCode(req.getLineCode()).setRemark(req.getRemark())
                .setStatus(MesProScanEventStatusEnum.FAILED).setReceivedTime(LocalDateTime.now());
        eventMapper.insert(event);
        MesProBagCodeDO bag = bagCodeMapper.selectByCodeForUpdate(req.getBagCode());
        if (bag == null) return failed(event, "MES_SCAN_BAG_NOT_EXISTS", "袋码不存在");
        event.setBagCodeId(bag.getId());
        if (bag.getInboundEventId() != null) return failed(event, "MES_SCAN_BAG_ALREADY_INBOUND", "袋码已入库");
        MesProBatchDO batch = batchMapper.selectByIdForUpdate(bag.getBatchId());
        if (batch == null) return failed(event, "MES_SCAN_BATCH_NOT_EXISTS", "生产批次不存在");
        event.setBatchId(batch.getId()).setBatchCode(batch.getCode()).setItemId(batch.getItemId());
        MesWmWarehouseDO warehouse = warehouseService.getWarehouseByCode(MesWmWarehouseDO.WIP_VIRTUAL_WAREHOUSE);
        if (warehouse == null) return failed(event, "MES_SCAN_WIP_WAREHOUSE_NOT_EXISTS", "WIP虚拟仓不存在");
        event.setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId());
        try {
            Long txId = transactionService.createTransaction(new MesWmTransactionSaveReqDTO()
                    .setType(MesWmTransactionTypeEnum.IN.getType()).setBizType(MesBizTypeConstants.WM_PRODUCT_PRODUCE)
                    .setBizId(batch.getId()).setBizCode(batch.getCode()).setBizLineId(bag.getId())
                    .setItemId(batch.getItemId()).setQuantity(req.getQuantity()).setBatchCode(batch.getCode())
                    .setWarehouseId(warehouse.getId()).setLocationId(req.getLocationId()).setAreaId(req.getAreaId())
                    .setReceiptTime(LocalDateTime.now()).setCheckFlag(true));
            event.setTransactionId(txId).setStatus(MesProScanEventStatusEnum.SUCCESS).setEventTime(LocalDateTime.now());
            eventMapper.updateById(event);
            bagCodeMapper.markInbound(bag.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            batchMapper.markInbound(batch.getId(), event.getId(), req.getDeviceCode(), req.getLineCode());
            return event;
        } catch (Exception ex) {
            return failed(event, "MES_SCAN_TRANSACTION_FAILED", "库存事务处理失败");
        }
    }
    private MesProScanEventDO failed(MesProScanEventDO event, String code, String message) {
        event.setStatus(MesProScanEventStatusEnum.FAILED).setErrorCode(code).setErrorMessage(message)
                .setFailedTime(LocalDateTime.now()).setEventTime(LocalDateTime.now());
        eventMapper.updateById(event);
        return event;
    }
    @Override public MesProScanEventDO getByEventId(String eventId) { return eventMapper.selectOne(MesProScanEventDO::getEventId, eventId); }
    @Override public PageResult<MesProScanEventDO> getPage(MesProScanEventPageReqVO reqVO) { return eventMapper.selectPage(reqVO); }
    @Override public MesProScanEventDO getLatest(String deviceCode) { return eventMapper.selectLatest(deviceCode); }
    @Override public Map<String, Object> getDashboard() {
        Map<String, Object> result = new HashMap<>();
        result.put("latest", eventMapper.selectLatest(null));
        result.put("total", eventMapper.selectCount(null));
        result.put("success", eventMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProScanEventDO>().eq(MesProScanEventDO::getStatus, MesProScanEventStatusEnum.SUCCESS)));
        result.put("failed", eventMapper.selectCount(new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<MesProScanEventDO>().eq(MesProScanEventDO::getStatus, MesProScanEventStatusEnum.FAILED)));
        return result;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityService.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,3 @@
package cn.iocoder.yudao.module.mes.service.qc.seedquality;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
public interface MesQcSeedQualityService { Long submit(MesQcSeedQualitySubmitReqVO reqVO); }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/seedquality/MesQcSeedQualityServiceImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,42 @@
package cn.iocoder.yudao.module.mes.service.qc.seedquality;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.qc.seedquality.vo.MesQcSeedQualitySubmitReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.seedquality.*;
import cn.iocoder.yudao.module.mes.dal.mysql.qc.seedquality.*;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.time.LocalDateTime;
import java.util.*;
@Service @Validated
public class MesQcSeedQualityServiceImpl implements MesQcSeedQualityService {
    private static final Set<String> REQUIRED = Set.of("purity", "cleanliness", "germination_rate", "moisture");
    @Resource private MesQcSeedQualityMapper qualityMapper;
    @Resource private MesQcSeedQualityIndicatorMapper indicatorMapper;
    @Override @Transactional(rollbackFor = Exception.class)
    public Long submit(MesQcSeedQualitySubmitReqVO reqVO) {
        if (reqVO.getIndicators().size() != 4) throw new IllegalArgumentException("必须提交四项指标");
        if (qualityMapper.selectByCode(reqVO.getCode()) != null) throw new IllegalArgumentException("该袋码已质检,禁止重复提交");
        Set<String> codes = new HashSet<>();
        boolean passed = true;
        MesQcSeedQualityDO quality = MesQcSeedQualityDO.builder().code(reqVO.getCode()).itemId(reqVO.getItemId())
                .batchCode(reqVO.getBatchCode()).reason(reqVO.getReason()).result(0)
                .inspectedAt(LocalDateTime.now()).build();
        qualityMapper.insert(quality);
        for (MesQcSeedQualitySubmitReqVO.Indicator item : reqVO.getIndicators()) {
            if (!REQUIRED.contains(item.getCode()) || !codes.add(item.getCode())) throw new IllegalArgumentException("指标必须为纯度、净度、发芽率、水分且不可重复");
            if (item.getMinValue().compareTo(item.getMaxValue()) > 0 || item.getValue().compareTo(item.getMinValue()) < 0 || item.getValue().compareTo(item.getMaxValue()) > 0) passed = false;
            indicatorMapper.insert(MesQcSeedQualityIndicatorDO.builder().qualityId(quality.getId()).indicatorCode(item.getCode())
                    .value(item.getValue()).minValue(item.getMinValue()).maxValue(item.getMaxValue()).build());
        }
        if (!codes.equals(REQUIRED)) throw new IllegalArgumentException("四项指标不完整");
        quality.setResult(passed ? 1 : 2);
        qualityMapper.updateById(quality);
        // åº“位由后台配置服务接管,当前版本保留审计闭环,禁止前端传入库位。
        return quality.getId();
    }
}