6 天以前 85156554438bb13930c89072ee0b7fa93192a7fe
1.联调优化采购申请到入库的整个逻辑
已添加10个文件
已修改32个文件
1413 ■■■■■ 文件已修改
docs/confirm_receipt_qc_flow.md 117 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/purchase_flow_frontend_guide.md 202 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/purchase_order_confirm_receipt.md 160 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/purchase_request_in_status.md 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java 40 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java 48 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/pom.xml 7 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java 45 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java 1 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java 10 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java 78 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java 43 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java 51 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java 9 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java 18 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java 57 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java 3 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java 83 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java 82 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java 29 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/confirm_receipt_qc_flow.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,117 @@
# ç¡®è®¤æ”¶è´§ä¸Žè´¨æ£€æµç¨‹ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- é‡‡è´­è®¢å•列表页
- é‡‡è´­è®¢å•详情页
- åˆ°è´§é€šçŸ¥å•列表页
- åˆ°è´§é€šçŸ¥å•详情页
- IQC æ¥æ–™æ£€éªŒå•页面
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
```
采购申请审批通过 â†’ ç”Ÿæˆé‡‡è´­è®¢å•(已审核) â†’ ç¡®è®¤æ”¶è´§
→ åˆ¤æ–­æ˜¯å¦å­˜åœ¨éœ€è´¨æ£€ç‰©æ–™
  â”œâ”€â”€ æ˜¯ â†’ ç”Ÿæˆåˆ°è´§é€šçŸ¥å•(待质检)→ è‡ªåŠ¨åˆ›å»º IQC å• â†’ IQC æ¥æ–™è´¨æ£€
  â”‚      â”œâ”€â”€ åˆæ ¼ â†’ ç­‰å¾…所有质检完成
  â”‚      â”œâ”€â”€ ç‰¹é‡‡ â†’ ç­‰å¾…所有质检完成
  â”‚      â”œâ”€â”€ ä¸åˆæ ¼ï¼ˆé€€è´§ï¼‰â†’ çŠ¶æ€ä¿æŒå¾…è´¨æ£€
  â”‚      â””── ä¸åˆæ ¼ï¼ˆæŠ¥åºŸï¼‰â†’ çŠ¶æ€ä¿æŒå¾…è´¨æ£€
  â”‚      â†“
  â”‚  æ‰€æœ‰éœ€æ£€ç‰©æ–™è´¨æ£€å®Œæˆä¸”可入库(合格/特采)→ åˆ°è´§é€šçŸ¥å•(待入库)
  â””── å¦ â†’ ç”Ÿæˆåˆ°è´§é€šçŸ¥å•(待入库)
```
**字段带入说明:**
| æºå•据 | ç›®æ ‡å•据 | å¸¦å…¥å­—段 |
|--------|----------|----------|
| é‡‡è´­è®¢å• | åˆ°è´§é€šçŸ¥å• | ä¾›åº”商、采购订单号 |
| é‡‡è´­è®¢å•明细 | åˆ°è´§é€šçŸ¥å•行 | ç‰©æ–™ã€åˆ°è´§æ•°é‡ã€è´¨æ£€æ ‡è¯†(qcCheckFlag) |
| åˆ°è´§é€šçŸ¥å•行 | IQC æ£€éªŒå• | ç‰©æ–™ã€åˆ°è´§æ•°é‡ã€ä¾›åº”商 |
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/purchase-order/confirm-receipt | ç¡®è®¤æ”¶è´§ |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é‡‡è´­è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": 123,  // ç”Ÿæˆçš„到货通知单编号
  "msg": "操作成功"
}
```
## å­—段展示规则
### é‡‡è´­è®¢å•明细新增字段
| å­—段 | ç±»åž‹ | å±•示位置 | è¯´æ˜Ž |
|------|------|----------|------|
| qcCheckFlag | Boolean | åˆ—表、详情、表单 | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼Œé»˜è®¤ false |
### åˆ°è´§é€šçŸ¥å•行新增字段
| å­—段 | ç±»åž‹ | å±•示位置 | è¯´æ˜Ž |
|------|------|----------|------|
| iqcCheckFlag | Boolean | åˆ—表、详情 | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼ˆä»Žé‡‡è´­è®¢å•带入) |
| iqcId | Long | è¯¦æƒ… | å…³è”çš„ IQC æ£€éªŒå•编号(自动创建后回写) |
| iqcCheckResult | Integer | åˆ—表、详情 | è´¨æ£€ç»“果:1合格 2特采 3不合格退货 4不合格报废 |
| qualifiedQuantity | BigDecimal | åˆ—表、详情 | åˆæ ¼æ•°é‡ï¼ˆè´¨æ£€å®ŒæˆåŽå›žå†™ï¼‰ |
### åˆ°è´§é€šçŸ¥å•状态流转
| çŠ¶æ€å€¼ | çŠ¶æ€å | è¯´æ˜Ž |
|--------|--------|------|
| 0 | è‰ç¨¿ | æ–°å»ºçŠ¶æ€ |
| 10 | å¾…质检 | å­˜åœ¨éœ€è´¨æ£€ç‰©æ–™ï¼Œç­‰å¾… IQC å®Œæˆ |
| 20 | å¾…入库 | æ— éœ€è´¨æ£€æˆ–所有质检完成且可入库 |
| 30 | å·²å®Œæˆ | å…¥åº“完成 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
### ç¡®è®¤æ”¶è´§æŒ‰é’®å±•示
| åœºæ™¯ | è§„则 |
|------|------|
| æ˜¾ç¤ºæ¡ä»¶ | è®¢å•状态=已审核(20) ä¸” å…¥åº“状态≠全部入库(2) |
| ç‚¹å‡»åŽ | è°ƒç”¨ confirmReceipt æŽ¥å£ï¼Œè¿”回到货通知单编号 |
| æˆåŠŸåŽ | æç¤º"确认收货成功,已生成到货通知单",刷新列表 |
### è´¨æ£€ç»“果对入库的影响
| è´¨æ£€ç»“æžœ | å€¼ | æ˜¯å¦å¯å…¥åº“ | è¯´æ˜Ž |
|----------|-----|------------|------|
| åˆæ ¼ | 1 | âœ… æ˜¯ | æ­£å¸¸å…¥åº“ |
| ç‰¹é‡‡ | 2 | âœ… æ˜¯ | ç‰¹æ®Šæ‰¹å‡†å…¥åº“ |
| ä¸åˆæ ¼é€€è´§ | 3 | âŒ å¦ | é€€å›žä¾›åº”商,不可入库 |
| ä¸åˆæ ¼æŠ¥åºŸ | 4 | âŒ å¦ | æŠ¥åºŸå¤„理,不可入库 |
### åˆ°è´§é€šçŸ¥å•状态推进规则
| å½“前状态 | æŽ¨è¿›æ¡ä»¶ | ç›®æ ‡çŠ¶æ€ |
|----------|----------|----------|
| è‰ç¨¿ | æäº¤æ—¶å­˜åœ¨éœ€è´¨æ£€è¡Œ | å¾…质检 |
| è‰ç¨¿ | æäº¤æ—¶æ— éœ€è´¨æ£€è¡Œ | å¾…入库 |
| å¾…质检 | æ‰€æœ‰éœ€æ£€è¡Œå®Œæˆè´¨æ£€ï¼Œä¸”都为合格/特采 | å¾…入库 |
| å¾…质检 | å­˜åœ¨é€€è´§/报废结果的行 | ä¿æŒå¾…质检 |
| å¾…入库 | æ‰‹åŠ¨å®Œæˆæ“ä½œ | å·²å®Œæˆ |
## æ³¨æ„äº‹é¡¹
1. **自动创建 IQC å•**:确认收货时,如果采购订单明细设置了 `qcCheckFlag=true`,系统会自动创建对应的 IQC æ£€éªŒå•,无需手动创建。
2. **质检结果回写**:IQC æ£€éªŒå®ŒæˆåŽï¼Œä¼šè‡ªåŠ¨å›žå†™åˆ°è´§é€šçŸ¥å•è¡Œçš„ `iqcCheckResult` å’Œ `qualifiedQuantity` å­—段。
3. **部分收货**:一个采购订单可以多次确认收货(部分收货场景),每次生成独立的到货通知单。
4. **入库前提条件**:只有到货通知单状态为"待入库"时才能执行采购入库操作。
5. **质检失败处理**:如果质检结果为退货或报废,到货通知单状态保持"待质检",需要人工介入处理(如重新安排收货)。
docs/purchase_flow_frontend_guide.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,202 @@
# é‡‡è´­æµç¨‹å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- é‡‡è´­è®¢å•列表页
- é‡‡è´­è®¢å•详情页
- åˆ°è´§é€šçŸ¥å•列表页
- åˆ°è´§é€šçŸ¥å•详情页
## ä¸šåŠ¡æµç¨‹
```
采购申请审批通过 â†’ è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•(已审核)
    â†’ ç‚¹å‡»"确认收货" â†’ ç”Ÿæˆåˆ°è´§é€šçŸ¥å•(待入库/待质检)
    â†’ IQC质检(如需要) â†’ é‡‡è´­å…¥åº“
```
---
## ä¸€ã€é‡‡è´­è®¢å•模块
### 1. æ–°å¢žæŒ‰é’®ï¼šç¡®è®¤æ”¶è´§
**位置:** é‡‡è´­è®¢å•列表页、详情页
**展示条件:**
- è®¢å•状态 = å·²å®¡æ ¸(20)
- å…¥åº“状态 â‰  å…¨éƒ¨å…¥åº“(2)
**API:**
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/purchase-order/confirm-receipt | ç¡®è®¤æ”¶è´§ |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é‡‡è´­è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": 123,  // ç”Ÿæˆçš„到货通知单编号
  "msg": "操作成功"
}
```
### 2. æ–°å¢žå­—段:采购订单明细
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| qcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼Œé»˜è®¤ false |
**展示位置:** é‡‡è´­è®¢å•明细表格
---
## äºŒã€é‡‡è´­ç”³è¯·æ¨¡å—
### æ–°å¢žå­—段:采购申请明细
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| qcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼Œé»˜è®¤ false |
**展示位置:** é‡‡è´­ç”³è¯·æ˜Žç»†è¡¨æ ¼
### æ–°å¢žå­—段:采购申请主表
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| depositPrice | BigDecimal | å®šé‡‘金额,默认 0 |
**展示位置:** é‡‡è´­ç”³è¯·è¡¨å•
---
## ä¸‰ã€åˆ°è´§é€šçŸ¥å•模块
### 1. çŠ¶æ€æµè½¬è¯´æ˜Ž
| çŠ¶æ€å€¼ | çŠ¶æ€å | è¯´æ˜Ž |
|--------|--------|------|
| 0 | è‰ç¨¿ | æ–°å»ºçŠ¶æ€ |
| 2 | å¾…质检 | å­˜åœ¨éœ€è´¨æ£€ç‰©æ–™ï¼Œç­‰å¾… IQC å®Œæˆ |
| 3 | å¾…入库 | æ— éœ€è´¨æ£€æˆ–所有质检完成且可入库 |
| 4 | å·²å®Œæˆ | å…¥åº“完成 |
### 2. æ–°å¢žå­—段:到货通知单行
| å­—段 | ç±»åž‹ | å±•示位置 | è¯´æ˜Ž |
|------|------|----------|------|
| iqcCheckFlag | Boolean | åˆ—表、详情 | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ |
| iqcId | Long | è¯¦æƒ… | å…³è”çš„ IQC æ£€éªŒå•编号 |
| iqcCheckResult | Integer | åˆ—表、详情 | è´¨æ£€ç»“æžœ |
| qualifiedQuantity | BigDecimal | åˆ—表、详情 | åˆæ ¼æ•°é‡ |
### 3. è´¨æ£€ç»“果枚举
| å€¼ | åç§° | æ˜¯å¦å¯å…¥åº“ |
|-----|------|------------|
| 1 | åˆæ ¼ | âœ… æ˜¯ |
| 2 | ç‰¹é‡‡ | âœ… æ˜¯ |
| 3 | ä¸åˆæ ¼é€€è´§ | âŒ å¦ |
| 4 | ä¸åˆæ ¼æŠ¥åºŸ | âŒ å¦ |
### 4. æ–°å¢žæŽ¥å£ï¼šæ ¹æ®é‡‡è´­è®¢å•生成到货通知单
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /mes/wm/arrival-notice/generate-from-purchase-order | æ ¹æ®é‡‡è´­è®¢å•生成到货通知单 |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| purchaseOrderId | Long | æ˜¯ | é‡‡è´­è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": 123,  // ç”Ÿæˆçš„到货通知单编号
  "msg": "操作成功"
}
```
---
## å››ã€å­—段带入说明
### æ•°æ®æµè½¬æ˜ å°„
| æºå•据 | ç›®æ ‡å•据 | å¸¦å…¥å­—段 |
|--------|----------|----------|
| é‡‡è´­ç”³è¯·æ˜Žç»† | é‡‡è´­è®¢å•明细 | qcCheckFlag(质检标识) |
| é‡‡è´­ç”³è¯·ä¸»è¡¨ | é‡‡è´­è®¢å•主表 | depositPrice(定金金额) |
| é‡‡è´­è®¢å• | åˆ°è´§é€šçŸ¥å• | ä¾›åº”商、采购订单号 |
| é‡‡è´­è®¢å•明细 | åˆ°è´§é€šçŸ¥å•行 | ç‰©æ–™ã€åˆ°è´§æ•°é‡ã€qcCheckFlag |
| åˆ°è´§é€šçŸ¥å•行 | IQC æ£€éªŒå• | ç‰©æ–™ã€åˆ°è´§æ•°é‡ã€ä¾›åº”商 |
---
## äº”、按钮展示规则汇总
### é‡‡è´­è®¢å•列表/详情页
| æŒ‰é’® | æ˜¾ç¤ºæ¡ä»¶ |
|------|----------|
| ç¡®è®¤æ”¶è´§ | è®¢å•状态=已审核(20) ä¸” å…¥åº“状态≠全部入库(2) |
### é‡‡è´­ç”³è¯·æ˜Žç»†è¡¨æ ¼
| åˆ— | è¯´æ˜Ž |
|----|------|
| æ˜¯å¦è´¨æ£€ | å¼€å…³ç»„件,默认关闭 |
### é‡‡è´­è®¢å•明细表格
| åˆ— | è¯´æ˜Ž |
|----|------|
| æ˜¯å¦è´¨æ£€ | åªè¯»å±•示,从采购申请带入 |
### åˆ°è´§é€šçŸ¥å•行表格
| åˆ— | è¯´æ˜Ž |
|----|------|
| è´¨æ£€æ ‡è¯† | åªè¯»å±•示 |
| è´¨æ£€ç»“æžœ | åªè¯»å±•示,质检完成后回写 |
| åˆæ ¼æ•°é‡ | åªè¯»å±•示,质检完成后回写 |
---
## å…­ã€æ³¨æ„äº‹é¡¹
1. **确认收货成功后**:返回到货通知单编号,前端可提示并刷新列表,或跳转到货通知单详情
2. **一个采购订单可多次确认收货**:支持部分收货场景,每次生成独立的到货通知单
3. **质检自动创建**:确认收货时,系统自动创建 IQC æ£€éªŒå•,无需手动创建
4. **入库前提条件**:只有到货通知单状态为"待入库"时才能执行采购入库
5. **质检失败处理**:如果质检结果为退货或报废,到货通知单状态保持"待质检",需人工处理
---
## ä¸ƒã€æƒé™é…ç½®
| æƒé™æ ‡è¯† | è¯´æ˜Ž |
|----------|------|
| erp:purchase-order:confirm-receipt | ç¡®è®¤æ”¶è´§æƒé™ |
---
## å…«ã€API æ±‡æ€»
| æ¨¡å— | æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|------|
| é‡‡è´­è®¢å• | POST | /erp/purchase-order/confirm-receipt | ç¡®è®¤æ”¶è´§ |
| åˆ°è´§é€šçŸ¥å• | POST | /mes/wm/arrival-notice/generate-from-purchase-order | æ ¹æ®é‡‡è´­è®¢å•生成 |
docs/purchase_order_confirm_receipt.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,160 @@
# é‡‡è´­è®¢å•确认收货 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- é‡‡è´­è®¢å•列表页
- é‡‡è´­è®¢å•详情页
## ä¸šåŠ¡æµç¨‹
```
采购申请审批通过 â†’ ç”Ÿæˆé‡‡è´­è®¢å•(已审核) â†’ ç¡®è®¤æ”¶è´§ â†’ ç”Ÿæˆåˆ°è´§é€šçŸ¥å•(待入库/待质检) â†’ æ‰§è¡Œå…¥åº“
```
**确认收货后的自动处理:**
- å¦‚果采购订单明细设置了 `qcCheckFlag=true`(需要质检),到货通知单自动进入"待质检"状态,并创建 IQC æ£€éªŒå•
- å¦‚果采购订单明细设置了 `qcCheckFlag=false`(无需质检),到货通知单直接进入"待入库"状态
## æ–°å¢ž API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| POST | /erp/purchase-order/confirm-receipt | ç¡®è®¤æ”¶è´§ |
**请求参数:**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é‡‡è´­è®¢å•编号 |
**响应:**
```json
{
  "code": 0,
  "data": 123,  // ç”Ÿæˆçš„到货通知单编号
  "msg": "操作成功"
}
```
**业务规则:**
- åªæœ‰å·²å®¡æ ¸çŠ¶æ€çš„é‡‡è´­è®¢å•æ‰èƒ½ç¡®è®¤æ”¶è´§
- å·²å…¨éƒ¨å…¥åº“的采购订单不能确认收货
- ç¡®è®¤æ”¶è´§åŽè¿”回生成的到货通知单编号,前端可跳转到货通知单详情
## å­—段变更
### é‡‡è´­ç”³è¯·æ˜Žç»†æ–°å¢žå­—段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| qcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼Œé»˜è®¤ false |
### é‡‡è´­è®¢å•明细新增字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| qcCheckFlag | Boolean | æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒï¼Œä»Žé‡‡è´­ç”³è¯·å¸¦å…¥ |
### é‡‡è´­ç”³è¯·æ–°å¢žå­—段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| depositPrice | BigDecimal | å®šé‡‘金额,默认 0 |
### é‡‡è´­è®¢å•新增字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| depositPrice | BigDecimal | å®šé‡‘金额,从采购申请带入 |
## æŒ‰é’®å±•示规则
### é‡‡è´­è®¢å•列表/详情页
| æŒ‰é’® | æ˜¾ç¤ºæ¡ä»¶ |
|------|----------|
| ç¡®è®¤æ”¶è´§ | è®¢å•状态=已审核(20) ä¸” å…¥åº“状态≠全部入库(2) |
## å‰ç«¯ä¿®æ”¹ç‚¹
### 1. é‡‡è´­è®¢å•列表页 - æ·»åŠ ç¡®è®¤æ”¶è´§æŒ‰é’®
```html
<el-button
  type="success"
  v-if="scope.row.status === 20 && scope.row.inStatus !== 2"
  @click="handleConfirmReceipt(scope.row)"
  v-hasPermi="['erp:purchase-order:confirm-receipt']">
  ç¡®è®¤æ”¶è´§
</el-button>
```
### 2. æ–¹æ³•实现
```js
// ç¡®è®¤æ”¶è´§
async handleConfirmReceipt(row) {
  try {
    await this.$modal.confirm(`确认对采购订单【${row.no}】进行收货操作?`);
    const res = await confirmReceipt(row.id);
    this.$modal.msgSuccess('确认收货成功,已生成到货通知单');
    // å¯é€‰ï¼šè·³è½¬åˆ°è´§é€šçŸ¥å•详情
    // this.$router.push({ path: '/mes/wm/arrival-notice/detail', query: { id: res.data } });
    this.getList();
  } catch (e) {
    // å–消或失败
  }
}
```
### 3. API æŽ¥å£
```js
// api/erp/purchase/order.js
export function confirmReceipt(id) {
  return request({
    url: '/erp/purchase-order/confirm-receipt',
    method: 'post',
    params: { id }
  })
}
```
### 4. é‡‡è´­ç”³è¯·è¡¨å• - æ·»åŠ è´¨æ£€æ ‡è¯†å­—æ®µ
在采购申请明细表格中添加"是否质检"列:
```html
<el-table-column label="是否质检" prop="qcCheckFlag" width="100">
  <template #default="scope">
    <el-switch v-model="scope.row.qcCheckFlag" />
  </template>
</el-table-column>
```
### 5. é‡‡è´­ç”³è¯·è¡¨å• - æ·»åŠ å®šé‡‘å­—æ®µ
```html
<el-form-item label="定金金额" prop="depositPrice">
  <el-input-number
    v-model="form.depositPrice"
    :min="0"
    :precision="2"
    :controls="false"
    placeholder="请输入定金金额" />
</el-form-item>
```
## æƒé™é…ç½®
| æƒé™æ ‡è¯† | è¯´æ˜Ž |
|----------|------|
| erp:purchase-order:confirm-receipt | ç¡®è®¤æ”¶è´§æƒé™ |
## æ³¨æ„äº‹é¡¹
1. ç¡®è®¤æ”¶è´§æ“ä½œä¼šè‡ªåŠ¨ç”Ÿæˆåˆ°è´§é€šçŸ¥å•ï¼Œæ— éœ€æ‰‹åŠ¨åˆ›å»º
2. è´¨æ£€æ ‡è¯†ä»Žé‡‡è´­ç”³è¯· â†’ é‡‡è´­è®¢å• â†’ åˆ°è´§é€šçŸ¥å•全程传递
3. å®šé‡‘金额从采购申请自动带入采购订单
4. ä¸€ä¸ªé‡‡è´­è®¢å•可以多次确认收货(部分收货场景)
docs/purchase_request_in_status.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,97 @@
# é‡‡è´­å…¥åº“状态回写 - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- é‡‡è´­ç”³è¯·åˆ—表页
- é‡‡è´­ç”³è¯·è¯¦æƒ…页
- é‡‡è´­è®¢å•列表页
- é‡‡è´­è®¢å•详情页
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
```
采购申请审批通过 â†’ ç”Ÿæˆé‡‡è´­è®¢å•(已审核) â†’ ç¡®è®¤æ”¶è´§ â†’ ç”Ÿæˆåˆ°è´§é€šçŸ¥å•(待入库) â†’ æ‰§è¡Œå…¥åº“
       â†“                    â†“                                           â†“
   order_id/order_no   request_item_id å…³è”                         in_status å›žå†™
```
**状态回写流程:**
1. å…¥åº“单完成 â†’ å›žå†™é‡‡è´­è®¢å•明细入库数量(in_count)
2. å…¥åº“单完成 â†’ å›žå†™é‡‡è´­è®¢å•入库状态(in_status)
3. å…¥åº“单完成 â†’ å›žå†™é‡‡è´­ç”³è¯·æ˜Žç»†å…¥åº“数量(in_count)
4. å…¥åº“单完成 â†’ å›žå†™é‡‡è´­ç”³è¯·å…¥åº“状态(in_status)
## å­—段变更
### é‡‡è´­ç”³è¯·æ–°å¢žå­—段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| inStatus | Integer | å…¥åº“状态(0:未入库 1:部分入库 2:全部入库) |
### é‡‡è´­ç”³è¯·æ˜Žç»†æ–°å¢žå­—段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| inCount | BigDecimal | å…¥åº“数量 |
### é‡‡è´­è®¢å•明细新增字段
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| requestItemId | Long | é‡‡è´­ç”³è¯·æ˜Žç»†ç¼–号(系统自动关联) |
## å“åº”字段变更
### é‡‡è´­ç”³è¯·è¯¦æƒ…响应新增
```json
{
  "inStatus": 0
}
```
### é‡‡è´­ç”³è¯·æ˜Žç»†å“åº”新增
```json
{
  "items": [
    {
      "inCount": 0
    }
  ]
}
```
### é‡‡è´­è®¢å•详情响应新增
无变化(`requestItemId` ä¸ºç³»ç»Ÿå†…部关联,前端无需展示)
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| å…¥åº“状态 | é‡‡è´­ç”³è¯·åˆ—表、详情 | ä½¿ç”¨æ ‡ç­¾å±•示:未入库/部分入库/全部入库 |
| å…¥åº“数量 | é‡‡è´­ç”³è¯·æ˜Žç»†è¡¨æ ¼ | æ•°å€¼å±•示,保留6位小数 |
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| é‡‡è´­ç”³è¯·å…¥åº“状态 | æ ¹æ®é‡‡è´­è®¢å•入库状态自动更新,无需手动操作 |
| é‡‡è´­ç”³è¯·æ˜Žç»†å…¥åº“数量 | æ ¹æ®é‡‡è´­è®¢å•明细入库数量累加更新 |
| å¤šæ¬¡å…¥åº“ | æ”¯æŒéƒ¨åˆ†å…¥åº“,入库状态自动流转 |
## å…¥åº“状态枚举
| å€¼ | åç§° | é¢œè‰²å»ºè®® |
|----|------|----------|
| 0 | æœªå…¥åº“ | info(灰色) |
| 1 | éƒ¨åˆ†å…¥åº“ | warning(橙色) |
| 2 | å…¨éƒ¨å…¥åº“ | success(绿色) |
## æ³¨æ„äº‹é¡¹
1. å…¥åº“状态由系统自动回写,无需手动维护
2. ä¸€ä¸ªé‡‡è´­ç”³è¯·å¯¹åº”一个采购订单,入库状态同步
3. å¦‚果采购申请未指定供应商,审批通过后需要手动选择供应商生成订单
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,40 @@
package cn.iocoder.yudao.module.erp.api.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import java.util.Collection;
import java.util.List;
/**
 * ERP ä¾›åº”商 API æŽ¥å£
 */
public interface ErpSupplierApi {
    String PREFIX = "/rpc-api/erp/supplier";
    /**
     * èŽ·å–ä¾›åº”å•†
     *
     * @param id ä¾›åº”商编号
     * @return ä¾›åº”商
     */
    CommonResult<ErpSupplierRespDTO> getSupplier(Long id);
    /**
     * æ ¡éªŒä¾›åº”商存在
     *
     * @param id ä¾›åº”商编号
     * @return ä¾›åº”商
     */
    CommonResult<ErpSupplierRespDTO> validateSupplier(Long id);
    /**
     * èŽ·å–ä¾›åº”å•†åˆ—è¡¨
     *
     * @param ids ç¼–号列表
     * @return ä¾›åº”商列表
     */
    CommonResult<List<ErpSupplierRespDTO>> getSupplierList(Collection<Long> ids);
}
yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/dto/ErpSupplierRespDTO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.erp.api.purchase.dto;
import lombok.Data;
/**
 * ERP ä¾›åº”商 Response DTO
 */
@Data
public class ErpSupplierRespDTO {
    /**
     * ä¾›åº”商编号
     */
    private Long id;
    /**
     * ä¾›åº”商名称
     */
    private String name;
    /**
     * è”系人
     */
    private String contact;
    /**
     * æ‰‹æœºå·ç 
     */
    private String mobile;
    /**
     * è”系电话
     */
    private String telephone;
    /**
     * ç”µå­é‚®ç®±
     */
    private String email;
    /**
     * å¼€å¯çŠ¶æ€
     */
    private Integer status;
    /**
     * çº³ç¨Žäººè¯†åˆ«å·
     */
    private String taxNo;
    /**
     * ç¨Žçއ
     */
    private java.math.BigDecimal taxPercent;
}
yudao-module-erp/pom.xml
@@ -52,6 +52,13 @@
            <version>${revision}</version>
        </dependency>
        <!-- MES åˆ¶é€ æ‰§è¡Œç³»ç»Ÿ API -->
        <dependency>
            <groupId>cn.iocoder.boot</groupId>
            <artifactId>yudao-module-mes-api</artifactId>
            <version>${revision}</version>
        </dependency>
        <!-- ä¸šåŠ¡ç»„ä»¶ -->
        <!-- Web ç›¸å…³ -->
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpSupplierApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.erp.api.purchase;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpSupplierDO;
import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
import java.util.Collection;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
/**
 * ERP ä¾›åº”商 API å®žçŽ°ç±»
 */
@Service
@Validated
public class ErpSupplierApiImpl implements ErpSupplierApi {
    @Resource
    private ErpSupplierService supplierService;
    @Override
    public CommonResult<ErpSupplierRespDTO> getSupplier(Long id) {
        ErpSupplierDO supplier = supplierService.getSupplier(id);
        return success(BeanUtils.toBean(supplier, ErpSupplierRespDTO.class));
    }
    @Override
    public CommonResult<ErpSupplierRespDTO> validateSupplier(Long id) {
        ErpSupplierDO supplier = supplierService.validateSupplier(id);
        return success(BeanUtils.toBean(supplier, ErpSupplierRespDTO.class));
    }
    @Override
    public CommonResult<List<ErpSupplierRespDTO>> getSupplierList(Collection<Long> ids) {
        List<ErpSupplierDO> list = supplierService.getSupplierList(ids);
        return success(BeanUtils.toBean(list, ErpSupplierRespDTO.class));
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -148,6 +148,14 @@
        ExcelUtils.write(response, "采购订单.xls", "数据", ErpPurchaseOrderRespVO.class, list);
    }
    @PostMapping("/confirm-receipt")
    @Operation(summary = "确认收货")
    @Parameter(name = "id", description = "采购订单编号", required = true)
    @PreAuthorize("@ss.hasPermission('erp:purchase-order:confirm-receipt')")
    public CommonResult<Long> confirmReceipt(@RequestParam("id") Long id) {
        return success(purchaseOrderService.confirmReceipt(id));
    }
    private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult) {
        if (CollUtil.isEmpty(pageResult.getList())) {
            return PageResult.empty(pageResult.getTotal());
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -43,6 +43,9 @@
    @Schema(description = "供应商名称", example = "供应商A")
    private String supplierName;
    @Schema(description = "定金金额", example = "1000.00")
    private BigDecimal depositPrice;
    @Schema(description = "合计数量", requiredMode = Schema.RequiredMode.REQUIRED)
    private BigDecimal totalCount;
@@ -113,6 +116,9 @@
        @Schema(description = "备注", example = "备注")
        private String remark;
        @Schema(description = "是否需要来料检验", example = "true")
        private Boolean qcCheckFlag;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -30,6 +30,9 @@
    @Schema(description = "优惠率,百分比", example = "10")
    private BigDecimal discountPercent;
    @Schema(description = "定金金额", example = "1000.00")
    private BigDecimal depositPrice;
    @Schema(description = "附件地址", example = "https://xxx.com/xxx.pdf")
    private String fileUrl;
@@ -72,6 +75,9 @@
        @Schema(description = "备注", example = "备注")
        private String remark;
        @Schema(description = "是否需要来料检验", example = "true")
        private Boolean qcCheckFlag;
        @Schema(description = "产品名称(前端展示用,后端忽略)", example = "产品A")
        private String productName;
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderItemDO.java
@@ -35,6 +35,12 @@
     */
    private Long orderId;
    /**
     * é‡‡è´­ç”³è¯·æ˜Žç»†ç¼–号
     *
     * å…³è” {@link ErpPurchaseRequestItemDO#getId()}
     */
    private Long requestItemId;
    /**
     * ç‰©æ–™ç¼–号
     *
     * å…³è” {@link cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO#getId()}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -100,6 +100,11 @@
    private BigDecimal discountPrice;
    /**
     * å®šé‡‘金额
     */
    private BigDecimal depositPrice;
    /**
     * é™„件地址
     */
    private String fileUrl;
@@ -124,4 +129,13 @@
     */
    private String orderNo;
    /**
     * å…¥åº“状态
     *
     * 0: æœªå…¥åº“
     * 1: éƒ¨åˆ†å…¥åº“
     * 2: å…¨éƒ¨å…¥åº“
     */
    private Integer inStatus;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestItemDO.java
@@ -78,4 +78,14 @@
     */
    private String remark;
    /**
     * æ˜¯å¦éœ€è¦æ¥æ–™æ£€éªŒ
     */
    private Boolean qcCheckFlag;
    /**
     * å…¥åº“数量
     */
    private BigDecimal inCount;
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -28,6 +28,7 @@
    ErrorCode PURCHASE_ORDER_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_030_101_011, "采购审核分类不存在,请先在BPM模块创建采购审核流程分类");
    ErrorCode PURCHASE_ORDER_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_101_012, "采购审核分类下没有可用的流程模型,请先创建并发布流程");
    ErrorCode PURCHASE_ORDER_SUBMIT_FAIL_STATUS = new ErrorCode(1_030_101_013, "提交审批失败,只有未审核状态的采购订单才能提交");
    ErrorCode PURCHASE_ORDER_ALL_IN = new ErrorCode(1_030_101_014, "采购订单已全部入库,无需确认收货");
    // ========== ERP é‡‡è´­å…¥åº“(1-030-102-000) ==========
    ErrorCode PURCHASE_IN_NOT_EXISTS = new ErrorCode(1_030_102_000, "采购入库单不存在");
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -160,4 +160,14 @@
     */
    void updatePurchaseOrderInStatus(Long orderId);
    /**
     * ç¡®è®¤æ”¶è´§
     *
     * ç”Ÿæˆåˆ°è´§é€šçŸ¥å•,状态为待入库
     *
     * @param id é‡‡è´­è®¢å•编号
     * @return åˆ°è´§é€šçŸ¥å•编号
     */
    Long confirmReceipt(Long id);
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -16,15 +16,21 @@
import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestDO;
import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseRequestItemDO;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseOrderMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestItemMapper;
import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseRequestMapper;
import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.flowable.engine.repository.ProcessDefinition;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
@@ -70,6 +76,15 @@
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Resource
    @Lazy
    private MesArrivalNoticeApi arrivalNoticeApi;
    @Resource
    private ErpPurchaseRequestItemMapper purchaseRequestItemMapper;
    @Resource
    private ErpPurchaseRequestMapper purchaseRequestMapper;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -436,6 +451,51 @@
        // 4. æ›´æ–°é‡‡è´­è®¢å•入库状态
        purchaseOrderMapper.updateById(new ErpPurchaseOrderDO()
                .setId(orderId).setInStatus(inStatus));
        // 5. å›žå†™é‡‡è´­ç”³è¯·æ˜Žç»†çš„入库数量和采购申请的入库状态
        updatePurchaseRequestInStatus(items, inStatus);
    }
    /**
     * å›žå†™é‡‡è´­ç”³è¯·å…¥åº“数量和入库状态
     *
     * @param orderItems é‡‡è´­è®¢å•明细列表
     * @param orderInStatus é‡‡è´­è®¢å•入库状态
     */
    private void updatePurchaseRequestInStatus(List<ErpPurchaseOrderItemDO> orderItems, int orderInStatus) {
        // èŽ·å–æ‰€æœ‰å…³è”çš„é‡‡è´­ç”³è¯·æ˜Žç»† ID
        Set<Long> requestItemIds = new HashSet<>();
        for (ErpPurchaseOrderItemDO item : orderItems) {
            if (item.getRequestItemId() != null) {
                requestItemIds.add(item.getRequestItemId());
            }
        }
        if (requestItemIds.isEmpty()) {
            return;
        }
        // æ›´æ–°é‡‡è´­ç”³è¯·æ˜Žç»†çš„入库数量
        for (ErpPurchaseOrderItemDO orderItem : orderItems) {
            if (orderItem.getRequestItemId() != null && orderItem.getInCount() != null) {
                purchaseRequestItemMapper.updateById(new ErpPurchaseRequestItemDO()
                        .setId(orderItem.getRequestItemId())
                        .setInCount(orderItem.getInCount()));
            }
        }
        // èŽ·å–é‡‡è´­ç”³è¯·æ˜Žç»†ï¼Œæ‰¾åˆ°å¯¹åº”çš„é‡‡è´­ç”³è¯· ID
        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectBatchIds(requestItemIds);
        if (CollUtil.isEmpty(requestItems)) {
            return;
        }
        Set<Long> requestIds = convertSet(requestItems, ErpPurchaseRequestItemDO::getRequestId);
        // æ›´æ–°é‡‡è´­ç”³è¯·çš„入库状态
        for (Long requestId : requestIds) {
            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                    .setId(requestId)
                    .setInStatus(orderInStatus));
        }
    }
    /**
@@ -469,4 +529,22 @@
        }
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long confirmReceipt(Long id) {
        // 1. æ ¡éªŒé‡‡è´­è®¢å•存在且已审核
        ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrder(id);
        // 2. æ ¡éªŒé‡‡è´­è®¢å•未全部入库
        if (ErpPurchaseOrderInStatusEnum.ALL_IN.getStatus().equals(purchaseOrder.getInStatus())) {
            throw exception(PURCHASE_ORDER_ALL_IN);
        }
        // 3. è°ƒç”¨ MES æŽ¥å£ç”Ÿæˆåˆ°è´§é€šçŸ¥å•
        Long arrivalNoticeId = arrivalNoticeApi.generateArrivalNoticeFromPurchaseOrder(id);
        log.info("[confirmReceipt] é‡‡è´­è®¢å•({}) ç¡®è®¤æ”¶è´§ï¼Œç”Ÿæˆåˆ°è´§é€šçŸ¥å•({})", id, arrivalNoticeId);
        return arrivalNoticeId;
    }
}
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseRequestServiceImpl.java
@@ -309,6 +309,19 @@
            purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                    .setId(id).setStatus(newStatus));
        }
        // 3. å®¡æ‰¹é€šè¿‡ä¸”有供应商,自动生成采购订单
        if (ErpPurchaseRequestStatusEnum.APPROVE.getStatus().equals(newStatus)
                && purchaseRequest.getSupplierId() != null
                && purchaseRequest.getOrderId() == null) {
            try {
                Long orderId = doGeneratePurchaseOrder(purchaseRequest);
                log.info("[updatePurchaseRequestAuditStatus] é‡‡è´­ç”³è¯·({}) è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•({})", id, orderId);
            } catch (Exception e) {
                log.error("[updatePurchaseRequestAuditStatus] é‡‡è´­ç”³è¯·({}) è‡ªåŠ¨ç”Ÿæˆé‡‡è´­è®¢å•å¤±è´¥", id, e);
                // ä¸æŠ›å‡ºå¼‚常,避免影响状态更新
            }
        }
    }
    @Override
@@ -326,23 +339,37 @@
        // 3. æ ¡éªŒä¾›åº”商
        supplierService.validateSupplier(supplierId);
        // 4. èŽ·å–ç”³è¯·æ˜Žç»†
        // 4. è®¾ç½®ä¾›åº”商并生成订单
        purchaseRequest.setSupplierId(supplierId);
        return doGeneratePurchaseOrder(purchaseRequest);
    }
    /**
     * æ‰§è¡Œç”Ÿæˆé‡‡è´­è®¢å•
     *
     * @param purchaseRequest é‡‡è´­ç”³è¯·ï¼ˆéœ€åŒ…含 supplierId)
     * @return é‡‡è´­è®¢å• ID
     */
    private Long doGeneratePurchaseOrder(ErpPurchaseRequestDO purchaseRequest) {
        Long id = purchaseRequest.getId();
        // 1. èŽ·å–ç”³è¯·æ˜Žç»†
        List<ErpPurchaseRequestItemDO> requestItems = purchaseRequestItemMapper.selectListByRequestId(id);
        if (CollUtil.isEmpty(requestItems)) {
            throw exception(PURCHASE_REQUEST_NOT_EXISTS);
        }
        // 5. ç”Ÿæˆé‡‡è´­è®¢å•号
        // 2. ç”Ÿæˆé‡‡è´­è®¢å•号
        String orderNo = noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
        if (purchaseOrderMapper.selectByNo(orderNo) != null) {
            throw exception(PURCHASE_ORDER_NO_EXISTS);
        }
        // 6. åˆ›å»ºé‡‡è´­è®¢å•
        // 3. åˆ›å»ºé‡‡è´­è®¢å•
        ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
        purchaseOrder.setNo(orderNo);
        purchaseOrder.setStatus(ErpAuditStatus.APPROVE.getStatus()); // é‡‡è´­è®¢å•直接为已审核状态
        purchaseOrder.setSupplierId(supplierId);
        purchaseOrder.setSupplierId(purchaseRequest.getSupplierId());
        purchaseOrder.setOrderTime(purchaseRequest.getRequestTime());
        purchaseOrder.setTotalCount(purchaseRequest.getTotalCount());
        purchaseOrder.setTotalPrice(purchaseRequest.getTotalPrice());
@@ -350,15 +377,17 @@
        purchaseOrder.setTotalTaxPrice(purchaseRequest.getTotalTaxPrice());
        purchaseOrder.setDiscountPercent(purchaseRequest.getDiscountPercent());
        purchaseOrder.setDiscountPrice(purchaseRequest.getDiscountPrice());
        purchaseOrder.setDepositPrice(purchaseRequest.getDepositPrice() != null ? purchaseRequest.getDepositPrice() : BigDecimal.ZERO);
        purchaseOrder.setRemark(purchaseRequest.getRemark());
        purchaseOrder.setInCount(BigDecimal.ZERO);
        purchaseOrder.setInStatus(0);
        purchaseOrderMapper.insert(purchaseOrder);
        // 7. åˆ›å»ºé‡‡è´­è®¢å•明细
        // 4. åˆ›å»ºé‡‡è´­è®¢å•明细
        List<ErpPurchaseOrderItemDO> orderItems = convertList(requestItems, item -> {
            ErpPurchaseOrderItemDO orderItem = new ErpPurchaseOrderItemDO();
            orderItem.setOrderId(purchaseOrder.getId());
            orderItem.setRequestItemId(item.getId()); // å…³è”采购申请明细
            orderItem.setProductId(item.getProductId());
            orderItem.setProductUnitId(item.getProductUnitId());
            orderItem.setProductPrice(item.getProductPrice());
@@ -368,12 +397,12 @@
            orderItem.setTaxPrice(item.getTaxPrice());
            orderItem.setRemark(item.getRemark());
            orderItem.setInCount(BigDecimal.ZERO);
            orderItem.setQcCheckFlag(false); // é»˜è®¤ä¸éœ€è¦è´¨æ£€
            orderItem.setQcCheckFlag(item.getQcCheckFlag() != null ? item.getQcCheckFlag() : false);
            return orderItem;
        });
        purchaseOrderItemMapper.insertBatch(orderItems);
        // 8. æ›´æ–°é‡‡è´­ç”³è¯·çš„订单信息
        // 5. æ›´æ–°é‡‡è´­ç”³è¯·çš„订单信息
        purchaseRequestMapper.updateById(new ErpPurchaseRequestDO()
                .setId(id)
                .setOrderId(purchaseOrder.getId())
yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseRequestStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.erp.service.purchase.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseRequestService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * é‡‡è´­ç”³è¯·å®¡æ‰¹çŠ¶æ€ç›‘å¬å™¨
 *
 * ç›‘听 BPM æµç¨‹å®žä¾‹çŠ¶æ€å˜æ›´äº‹ä»¶ï¼Œæ›´æ–°é‡‡è´­ç”³è¯·çš„å®¡æ‰¹çŠ¶æ€
 */
@Component
@Slf4j
public class ErpPurchaseRequestStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
    /**
     * é‡‡è´­ç”³è¯·å®¡æ‰¹æµç¨‹å®šä¹‰ Key
     */
    private static final String PROCESS_DEFINITION_KEY = "purchase_request_approve";
    @Resource
    private ErpPurchaseRequestService purchaseRequestService;
    @Override
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long requestId;
        try {
            requestId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„采购申请编号", event.getBusinessKey());
            return;
        }
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,requestId={}, status={}", requestId, event.getStatus());
        try {
            purchaseRequestService.updatePurchaseRequestAuditStatus(requestId, event.getStatus());
            log.info("[onEvent] é‡‡è´­ç”³è¯·({}) å®¡æ‰¹çŠ¶æ€æ›´æ–°å®Œæˆ", requestId);
        } catch (Exception e) {
            log.error("[onEvent] æ›´æ–°é‡‡è´­ç”³è¯·å®¡æ‰¹çŠ¶æ€å¤±è´¥ï¼ŒrequestId={}", requestId, e);
        }
    }
}
yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApi.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,16 @@
package cn.iocoder.yudao.module.mes.api.arrivalnotice;
/**
 * MES åˆ°è´§é€šçŸ¥å• API æŽ¥å£
 */
public interface MesArrivalNoticeApi {
    /**
     * æ ¹æ®é‡‡è´­è®¢å•生成到货通知单(待入库状态)
     *
     * @param purchaseOrderId é‡‡è´­è®¢å•编号
     * @return ç”Ÿæˆçš„到货通知单编号
     */
    Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/arrivalnotice/MesArrivalNoticeApiImpl.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.mes.api.arrivalnotice;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
/**
 * MES åˆ°è´§é€šçŸ¥å• API å®žçŽ°ç±»
 */
@Service
public class MesArrivalNoticeApiImpl implements MesArrivalNoticeApi {
    @Resource
    private MesWmArrivalNoticeService arrivalNoticeService;
    @Override
    public Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId) {
        return arrivalNoticeService.generateArrivalNoticeFromPurchaseOrder(purchaseOrderId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/arrivalnotice/MesWmArrivalNoticeController.java
@@ -11,12 +11,12 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticePageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticeRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.arrivalnotice.vo.MesWmArrivalNoticeSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -45,7 +45,7 @@
    @Resource
    private MesWmArrivalNoticeService arrivalNoticeService;
    @Resource
    private MesMdVendorService vendorService;
    private ErpSupplierApi supplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
@@ -129,9 +129,10 @@
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
                convertSet(list, MesWmArrivalNoticeDO::getVendorId));
        // 1. èŽ·å¾—ä¾›åº”å•†ä¿¡æ¯ï¼ˆERP é‡‡è´­ä¾›åº”商)
        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
                convertSet(list, MesWmArrivalNoticeDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
        // 2. èŽ·å¾—é‡‡è´­è®¢å•ä¿¡æ¯
        List<Long> purchaseOrderIds = list.stream()
                .map(MesWmArrivalNoticeDO::getPurchaseOrderId)
@@ -143,8 +144,8 @@
                .collect(java.util.stream.Collectors.toMap(ErpPurchaseOrderRespDTO::getId, v -> v, (a, b) -> a));
        // 3. æž„建结果
        return BeanUtils.toBean(list, MesWmArrivalNoticeRespVO.class, vo -> {
            MapUtils.findAndThen(vendorMap, vo.getVendorId(), vendor ->
                    vo.setVendorCode(vendor.getCode()).setVendorName(vendor.getName()));
            MapUtils.findAndThen(supplierMap, vo.getVendorId(), supplier ->
                    vo.setVendorName(supplier.getName()));
            MapUtils.findAndThen(purchaseOrderMap, vo.getPurchaseOrderId(), purchaseOrder ->
                    vo.setPurchaseOrderNo(purchaseOrder.getNo()));
        });
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/itemreceipt/MesWmItemReceiptController.java
@@ -11,14 +11,14 @@
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.itemreceipt.vo.MesWmItemReceiptSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.vendor.MesMdVendorDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.qc.iqc.MesQcIqcDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.arrivalnotice.MesWmArrivalNoticeDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.itemreceipt.MesWmItemReceiptDO;
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.itemreceipt.MesWmItemReceiptService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpSupplierRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
@@ -47,7 +47,7 @@
    @Resource
    private MesWmItemReceiptService itemReceiptService;
    @Resource
    private MesMdVendorService vendorService;
    private ErpSupplierApi supplierApi;
    @Resource
    private MesQcIqcService iqcService;
    @Resource
@@ -171,17 +171,18 @@
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        // 1. èŽ·å¾—å…³è”æ•°æ®
        Map<Long, MesMdVendorDO> vendorMap = vendorService.getVendorMap(
                convertSet(list, MesWmItemReceiptDO::getVendorId));
        // 1. èŽ·å¾—å…³è”æ•°æ®ï¼ˆé‡‡è´­å…¥åº“ä½¿ç”¨ ERP ä¾›åº”商)
        Map<Long, ErpSupplierRespDTO> supplierMap = supplierApi.getSupplierList(
                convertSet(list, MesWmItemReceiptDO::getVendorId)).getCheckedData().stream()
                .collect(java.util.stream.Collectors.toMap(ErpSupplierRespDTO::getId, v -> v, (a, b) -> a));
        Map<Long, MesQcIqcDO> iqcMap = iqcService.getIqcMap(
                convertSet(list, MesWmItemReceiptDO::getIqcId));
        Map<Long, MesWmArrivalNoticeDO> noticeMap = arrivalNoticeService.getArrivalNoticeMap(
                convertSet(list, MesWmItemReceiptDO::getNoticeId));
        // 2. æž„建结果
        return BeanUtils.toBean(list, MesWmItemReceiptRespVO.class, vo -> {
            MapUtils.findAndThen(vendorMap, vo.getVendorId(),
                    vendor -> vo.setVendorName(vendor.getName()));
            MapUtils.findAndThen(supplierMap, vo.getVendorId(),
                    supplier -> vo.setVendorName(supplier.getName()));
            MapUtils.findAndThen(iqcMap, vo.getIqcId(),
                    iqc -> vo.setIqcCode(iqc.getCode()));
            MapUtils.findAndThen(noticeMap, vo.getNoticeId(), notice ->
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/arrivalnotice/MesWmArrivalNoticeLineDO.java
@@ -64,6 +64,15 @@
     * å…³è” {@link MesQcIqcDO#getId()}
     */
    private Long iqcId;
    /**
     * è´¨æ£€ç»“æžœ
     *
     * æžšä¸¾ {@link cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum}
     * 1-合格 2-特采 3-不合格退货 4-不合格报废
     */
    private Integer iqcCheckResult;
    /**
     * å¤‡æ³¨
     */
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/arrivalnotice/MesWmArrivalNoticeMapper.java
@@ -32,4 +32,8 @@
        return selectCount(MesWmArrivalNoticeDO::getVendorId, vendorId);
    }
    default MesWmArrivalNoticeDO selectByPurchaseOrderId(Long purchaseOrderId) {
        return selectOne(MesWmArrivalNoticeDO::getPurchaseOrderId, purchaseOrderId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/itemreceipt/MesWmItemReceiptMapper.java
@@ -36,4 +36,8 @@
        return selectList(MesWmItemReceiptDO::getVendorId, vendorId);
    }
    default MesWmItemReceiptDO selectByNoticeId(Long noticeId) {
        return selectOne(MesWmItemReceiptDO::getNoticeId, noticeId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/productsales/MesWmProductSalesMapper.java
@@ -31,6 +31,10 @@
        return selectOne(MesWmProductSalesDO::getCode, code);
    }
    default MesWmProductSalesDO selectByNoticeId(Long noticeId) {
        return selectOne(MesWmProductSalesDO::getNoticeId, noticeId);
    }
    default List<MesWmProductSalesDO> selectListByClientId(Long clientId) {
        return selectList(MesWmProductSalesDO::getClientId, clientId);
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/salesnotice/MesWmSalesNoticeMapper.java
@@ -27,4 +27,8 @@
        return selectOne(MesWmSalesNoticeDO::getCode, code);
    }
    default MesWmSalesNoticeDO selectBySaleOrderId(Long saleOrderId) {
        return selectOne(MesWmSalesNoticeDO::getSaleOrderId, saleOrderId);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -341,6 +341,7 @@
    ErrorCode QC_IQC_NOT_PREPARE = new ErrorCode(1_040_603_002, "只有草稿状态的检验单才可操作");
    ErrorCode QC_IQC_QUANTITY_MISMATCH = new ErrorCode(1_040_603_004, "合格品与不合格品数量之和须等于检测数量");
    ErrorCode QC_NO_TEMPLATE = new ErrorCode(1_040_603_005, "当前产品未配置检测模板");
    ErrorCode QC_NO_TEMPLATE_FOR_ITEM = new ErrorCode(1_040_603_008, "物料【{}】未配置 IQC æ£€æµ‹æ¨¡æ¿ï¼Œè¯·å…ˆåœ¨è´¨é‡ç®¡ç†ä¸­é…ç½®");
    ErrorCode QC_IQC_SOURCE_DOC_PARAMS_MISSING = new ErrorCode(1_040_603_006, "来源单据类型非空时,来源单据 ID å’Œæ¥æºå•据行 ID ä¸èƒ½ä¸ºç©º");
    ErrorCode QC_IQC_CHECK_RESULT_EMPTY = new ErrorCode(1_040_603_007, "完成检验单前,检测结果必须填写");
    // ========== MES è´¨é‡ç®¡ç†-来料检验行(1-040-603-100) ==========
@@ -451,6 +452,7 @@
    ErrorCode WM_ARRIVAL_NOTICE_IQC_PENDING = new ErrorCode(1_040_704_005, "存在待检验行,无法审批通过");
    ErrorCode WM_ARRIVAL_NOTICE_NO_LINE = new ErrorCode(1_040_704_006, "至少需要一条行项目");
    ErrorCode WM_ARRIVAL_NOTICE_VENDOR_MISMATCH = new ErrorCode(1_040_704_007, "到货通知单的供应商与当前单据不一致");
    ErrorCode WM_ARRIVAL_NOTICE_ALREADY_EXISTS = new ErrorCode(1_040_704_008, "该采购订单已生成到货通知单,不能重复生成");
    ErrorCode WM_ARRIVAL_NOTICE_LINE_NOT_EXISTS = new ErrorCode(1_040_704_100, "到货通知单行不存在");
    ErrorCode WM_ARRIVAL_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_704_101, "到货通知单行不属于指定的到货通知单");
@@ -462,7 +464,8 @@
    ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_705_004, "明细上架总数与行入库数量不匹配");
    ErrorCode WM_ITEM_RECEIPT_STATUS_ERROR = new ErrorCode(1_040_705_005, "入库单状态不正确");
    ErrorCode WM_ITEM_RECEIPT_CANCEL_NOT_ALLOWED = new ErrorCode(1_040_705_006, "已完成或已取消的入库单不允许取消");
    ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_007, "采购订单不存在");
    ErrorCode WM_ITEM_RECEIPT_ALREADY_EXISTS = new ErrorCode(1_040_705_007, "该到货通知单已生成入库单,不能重复生成");
    ErrorCode WM_PURCHASE_ORDER_NOT_EXISTS = new ErrorCode(1_040_705_008, "采购订单不存在");
    ErrorCode WM_PURCHASE_ORDER_NO_ITEM = new ErrorCode(1_040_705_008, "采购订单无明细数据");
    ErrorCode WM_ITEM_RECEIPT_LINE_NOT_EXISTS = new ErrorCode(1_040_705_100, "采购入库单行不存在");
    ErrorCode WM_ITEM_RECEIPT_LINE_ARRIVAL_NOTICE_LINE_REQUIRED = new ErrorCode(1_040_705_101,
@@ -471,6 +474,7 @@
            "入库单未关联到货通知单,不能选择到货通知单行");
    ErrorCode WM_ITEM_RECEIPT_DETAIL_NOT_EXISTS = new ErrorCode(1_040_705_200, "采购入库明细不存在");
    ErrorCode WM_ITEM_RECEIPT_DETAIL_QUANTITY_EXCEED = new ErrorCode(1_040_705_202, "上架明细总数量不能超过行入库数量");
    ErrorCode WM_ITEM_RECEIPT_BATCH_REQUIRED = new ErrorCode(1_040_705_203, "物料【{}-{}】已开启批次管理,上架明细的批次号不能为空");
    // ========== MES ä»“库管理-领料申请单(1-040-706-000) ==========
    ErrorCode WM_MATERIAL_REQUEST_NOT_EXISTS = new ErrorCode(1_040_706_000, "领料申请单不存在");
@@ -635,8 +639,9 @@
    ErrorCode WM_PRODUCT_SALES_CANNOT_FINISH = new ErrorCode(1_040_714_007, "当前状态不允许执行出库");
    ErrorCode WM_PRODUCT_SALES_CANNOT_CANCEL = new ErrorCode(1_040_714_008, "当前状态不允许取消");
    ErrorCode WM_PRODUCT_SALES_CANNOT_CONFIRM = new ErrorCode(1_040_714_009, "当前状态不允许确认检验通过");
    ErrorCode WM_PRODUCT_SALES_ALREADY_EXISTS = new ErrorCode(1_040_714_010, "该发货通知单已生成销售出库单,不能重复生成");
    ErrorCode WM_PRODUCT_SALES_DETAILS_EMPTY = new ErrorCode(1_040_714_020, "拣货明细不能为空");
    ErrorCode WM_PRODUCT_SALES_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_714_010, "拣货数量与出库数量不匹配");
    ErrorCode WM_PRODUCT_SALES_DETAIL_QUANTITY_MISMATCH = new ErrorCode(1_040_714_011, "拣货数量与出库数量不匹配");
    ErrorCode WM_PRODUCT_SALES_LINE_NOT_EXISTS = new ErrorCode(1_040_714_011, "销售出库单行不存在");
    ErrorCode WM_PRODUCT_SALES_DETAIL_NOT_EXISTS = new ErrorCode(1_040_714_012, "销售出库明细不存在");
    ErrorCode WM_PRODUCT_SALES_STOCK_INSUFFICIENT = new ErrorCode(1_040_714_013, "库存不足,无法拣货");
@@ -690,6 +695,7 @@
    ErrorCode WM_SALES_NOTICE_LINE_NOT_MATCH = new ErrorCode(1_040_720_012, "发货通知单行不属于指定的发货通知单");
    ErrorCode WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS = new ErrorCode(1_040_720_013, "销售订单不存在");
    ErrorCode WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY = new ErrorCode(1_040_720_014, "销售订单明细为空");
    ErrorCode WM_SALES_NOTICE_ALREADY_EXISTS = new ErrorCode(1_040_720_015, "该销售订单已生成发货通知单,不能重复生成");
    // ========== MES ä»“库管理-条码配置(1-040-730-000) ==========
    ErrorCode WM_BARCODE_CONFIG_NOT_EXISTS = new ErrorCode(1_040_730_000, "条码配置不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/qc/MesQcCheckResultEnum.java
@@ -8,15 +8,15 @@
/**
 * MES æ£€æµ‹ç»“果枚举
 *
 * @author èŠ‹é“æºç 
 */
@Getter
@AllArgsConstructor
public enum MesQcCheckResultEnum implements ArrayValuable<Integer> {
    PASS(1, "检验通过"),
    FAIL(2, "检验不通过");
    QUALIFIED(1, "合格"),
    SPECIAL_APPROVAL(2, "特采"),
    RETURN(3, "不合格退货"),
    SCRAP(4, "不合格报废");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesQcCheckResultEnum::getType).toArray(Integer[]::new);
@@ -34,4 +34,14 @@
        return ARRAYS;
    }
    /**
     * åˆ¤æ–­æ˜¯å¦å¯ä»¥å…¥åº“
     *
     * @param type æ£€æµ‹ç»“æžœ
     * @return true å¯ä»¥å…¥åº“
     */
    public static boolean canInbound(Integer type) {
        return QUALIFIED.getType().equals(type) || SPECIAL_APPROVAL.getType().equals(type);
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/qc/iqc/MesQcIqcServiceImpl.java
@@ -23,6 +23,8 @@
import cn.iocoder.yudao.module.mes.service.qc.template.MesQcTemplateItemService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.outsourcereceipt.MesWmOutsourceReceiptService;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
@@ -72,6 +74,13 @@
    @Resource
    private AdminUserApi adminUserApi;
    @Resource
    private MdmItemApi mdmItemApi;
    @Resource
    @Lazy
    private ErpSupplierApi supplierApi;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createIqc(MesQcIqcSaveReqVO createReqVO) {
@@ -117,9 +126,20 @@
    private void validateIqcSaveData(MesQcIqcSaveReqVO reqVO) {
        // æ ¡éªŒç¼–号唯一
        validateIqcCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商、物料、检测人员存在
        vendorService.validateVendorExists(reqVO.getVendorId());
        itemService.validateItemExists(reqVO.getItemId());
        // æ ¡éªŒä¾›åº”商存在(优先校验 ERP ä¾›åº”商,失败则校验 MDM ä¾›åº”商)
        if (reqVO.getVendorId() != null) {
            try {
                supplierApi.validateSupplier(reqVO.getVendorId());
            } catch (Exception e) {
                // ERP ä¾›åº”商校验失败,尝试 MDM ä¾›åº”商
                vendorService.validateVendorExists(reqVO.getVendorId());
            }
        }
        // æ ¡éªŒç‰©æ–™å­˜åœ¨ï¼ˆä½¿ç”¨ MDM ç‰©æ–™ï¼‰
        if (reqVO.getItemId() != null) {
            mdmItemApi.getItem(reqVO.getItemId()).getCheckedData();
        }
        // æ ¡éªŒæ£€æµ‹äººå‘˜å­˜åœ¨
        adminUserApi.validateUser(reqVO.getInspectorUserId());
    }
@@ -161,7 +181,7 @@
        // æ ¹æ®ä¸åŒçš„æ¥æºå•据类型,回写不同的单据
        if (Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_ARRIVAL_NOTICE)) {
            arrivalNoticeService.updateArrivalNoticeWhenIqcFinish(iqc.getSourceDocId(), iqc.getSourceLineId(),
                    iqc.getId(), iqc.getQualifiedQuantity());
                    iqc.getId(), iqc.getCheckResult(), iqc.getQualifiedQuantity());
        } else if (Objects.equals(iqc.getSourceDocType(), MesBizTypeConstants.WM_OUTSOURCE_RECPT)) {
            outsourceReceiptService.updateOutsourceReceiptWhenIqcFinish(iqc.getSourceDocId(), iqc.getSourceLineId(),
                    iqc.getId(), iqc.getQualifiedQuantity(), iqc.getUnqualifiedQuantity());
@@ -313,8 +333,15 @@
    @Transactional(rollbackFor = Exception.class)
    public Long createIqcFromArrivalNotice(Long noticeId, MesWmArrivalNoticeLineDO line, String noticeCode, Long vendorId) {
        // 1. æ ¡éªŒç‰©æ–™æ˜¯å¦é…ç½®äº† IQC æ£€éªŒæ¨¡æ¿
        MesQcTemplateItemDO templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                line.getItemId(), MesQcTypeEnum.IQC.getType());
        MesQcTemplateItemDO templateItem;
        try {
            templateItem = templateItemService.getRequiredTemplateByItemIdAndType(
                    line.getItemId(), MesQcTypeEnum.IQC.getType());
        } catch (Exception e) {
            // èŽ·å–ç‰©æ–™ä¿¡æ¯ï¼Œæä¾›æ›´æ˜Žç¡®çš„é”™è¯¯æç¤º
            String itemInfo = getItemInfo(line.getItemId());
            throw exception(QC_NO_TEMPLATE_FOR_ITEM, itemInfo);
        }
        Long templateId = templateItem.getTemplateId();
        // 2. ç”Ÿæˆæ£€éªŒå•编码
@@ -345,4 +372,22 @@
        return iqc.getId();
    }
    /**
     * èŽ·å–ç‰©æ–™ä¿¡æ¯ï¼ˆç¼–ç +名称)
     */
    private String getItemInfo(Long itemId) {
        if (itemId == null) {
            return "未知物料";
        }
        try {
            cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO item = mdmItemApi.getItem(itemId).getCheckedData();
            if (item != null) {
                return item.getCode() + "-" + item.getName();
            }
        } catch (Exception e) {
            // å¿½ç•¥å¼‚常
        }
        return "物料ID:" + itemId;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineService.java
@@ -72,9 +72,10 @@
     *
     * @param lineId è¡Œç¼–号
     * @param iqcId IQC æ£€éªŒå•编号
     * @param checkResult è´¨æ£€ç»“果(1合格 2特采 3不合格退货 4不合格报废)
     * @param qualifiedQuantity åˆæ ¼æ•°é‡
     */
    void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
    void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity);
    /**
     * æ ¡éªŒåˆ°è´§é€šçŸ¥å•行存在
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeLineServiceImpl.java
@@ -112,12 +112,12 @@
    }
    @Override
    public void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
    public void updateArrivalNoticeLineWhenIqcFinish(Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity) {
        // æ ¡éªŒè¡Œå­˜åœ¨
        validateArrivalNoticeLineExists(lineId);
        // æ›´æ–°
        // æ›´æ–°ï¼šç»‘定 iqcId + è´¨æ£€ç»“æžœ + åˆæ ¼æ•°é‡
        arrivalNoticeLineMapper.updateById(new MesWmArrivalNoticeLineDO()
                .setId(lineId).setIqcId(iqcId).setQualifiedQuantity(qualifiedQuantity));
                .setId(lineId).setIqcId(iqcId).setIqcCheckResult(checkResult).setQualifiedQuantity(qualifiedQuantity));
    }
    @Override
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeService.java
@@ -69,9 +69,10 @@
     * @param id åˆ°è´§é€šçŸ¥å•编号
     * @param lineId åˆ°è´§é€šçŸ¥å•行编号
     * @param iqcId IQC æ£€éªŒå•编号
     * @param checkResult è´¨æ£€ç»“果(1合格 2特采 3不合格退货 4不合格报废)
     * @param qualifiedQuantity åˆæ ¼æ•°é‡
     */
    void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity);
    void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity);
    /**
     * å®Œæˆåˆ°è´§é€šçŸ¥å•(待入库 â†’ å·²å®Œæˆï¼‰ï¼Œå†…部调用
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/arrivalnotice/MesWmArrivalNoticeServiceImpl.java
@@ -12,10 +12,12 @@
import cn.iocoder.yudao.module.mes.dal.mysql.wm.arrivalnotice.MesWmArrivalNoticeMapper;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmArrivalNoticeStatusEnum;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
import cn.iocoder.yudao.module.mes.service.mdm.MesMdmItemService;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.mes.enums.qc.MesQcCheckResultEnum;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Lazy;
@@ -44,8 +46,9 @@
    @Resource
    private MesWmArrivalNoticeLineService arrivalNoticeLineService;
    @Resource
    private cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService vendorService;
    private ErpSupplierApi supplierApi;
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
@@ -84,8 +87,8 @@
    private void validateArrivalNoticeSaveData(MesWmArrivalNoticeSaveReqVO reqVO) {
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在且启用
        vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
        // æ ¡éªŒä¾›åº”商存在(ERP é‡‡è´­ä¾›åº”商)
        supplierApi.validateSupplier(reqVO.getVendorId());
        // æ ¡éªŒé‡‡è´­è®¢å•存在
        if (reqVO.getPurchaseOrderId() != null) {
            ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
@@ -161,7 +164,7 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, BigDecimal qualifiedQuantity) {
    public void updateArrivalNoticeWhenIqcFinish(Long id, Long lineId, Long iqcId, Integer checkResult, BigDecimal qualifiedQuantity) {
        // 1.1 æ ¡éªŒåˆ°è´§é€šçŸ¥å•存在
        MesWmArrivalNoticeDO notice = validateArrivalNoticeExists(id);
        // 1.2 æ ¡éªŒçŠ¶æ€ä¸ºå¾…è´¨æ£€
@@ -169,18 +172,28 @@
            throw exception(WM_ARRIVAL_NOTICE_STATUS_NOT_PENDING_QC);
        }
        // 2. æ›´æ–°åˆ°è´§é€šçŸ¥å•行:绑定 iqcId + åˆæ ¼æ•°é‡
        arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, qualifiedQuantity);
        // 2. æ›´æ–°åˆ°è´§é€šçŸ¥å•行:绑定 iqcId + è´¨æ£€ç»“æžœ + åˆæ ¼æ•°é‡
        arrivalNoticeLineService.updateArrivalNoticeLineWhenIqcFinish(lineId, iqcId, checkResult, qualifiedQuantity);
        // 3.1 åˆ¤æ–­æ˜¯å¦æ‰€æœ‰éœ€æ£€è¡Œéƒ½å·²å®Œæˆ
        // 3.1 åˆ¤æ–­æ˜¯å¦æ‰€æœ‰éœ€æ£€è¡Œéƒ½å·²å®Œæˆè´¨æ£€
        List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(id);
        boolean hasUnchecked = CollectionUtils.anyMatch(lines,
                line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcId() == null);
                line -> Boolean.TRUE.equals(line.getIqcCheckFlag()) && line.getIqcCheckResult() == null);
        if (hasUnchecked) {
            log.info("[updateArrivalNoticeWhenIqcFinish][到货通知单({}) è¿˜æœ‰æœªå®Œæˆæ£€éªŒçš„行,暂不更新状态]", id);
            return;
        }
        // 3.2 æ‰€æœ‰è¡Œæ£€éªŒå®Œæˆï¼Œæ›´æ–°ä¸»è¡¨çŠ¶æ€ PENDING_QC â†’ PENDING_RECEIPT
        // 3.2 åˆ¤æ–­æ˜¯å¦æ‰€æœ‰éœ€æ£€è¡Œéƒ½å¯ä»¥å…¥åº“(合格或特采)
        boolean hasNonInbound = CollectionUtils.anyMatch(lines,
                line -> Boolean.TRUE.equals(line.getIqcCheckFlag())
                        && !MesQcCheckResultEnum.canInbound(line.getIqcCheckResult()));
        if (hasNonInbound) {
            log.info("[updateArrivalNoticeWhenIqcFinish][到货通知单({}) å­˜åœ¨ä¸å¯å…¥åº“的行(退货/报废),状态保持待质检]", id);
            return;
        }
        // 3.3 æ‰€æœ‰è¡Œæ£€éªŒå®Œæˆä¸”可入库,更新主表状态 PENDING_QC â†’ PENDING_RECEIPT
        arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
                .setId(id).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus()));
    }
@@ -285,44 +298,76 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generateArrivalNoticeFromPurchaseOrder(Long purchaseOrderId) {
        // 1. èŽ·å–é‡‡è´­è®¢å•
        // 1. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè¿‡åˆ°è´§é€šçŸ¥å•(防止重复生成)
        MesWmArrivalNoticeDO existNotice = arrivalNoticeMapper.selectByPurchaseOrderId(purchaseOrderId);
        if (existNotice != null) {
            throw exception(WM_ARRIVAL_NOTICE_ALREADY_EXISTS);
        }
        // 2. èŽ·å–é‡‡è´­è®¢å•
        ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(purchaseOrderId).getCheckedData();
        if (purchaseOrder == null) {
            throw exception(WM_PURCHASE_ORDER_NOT_EXISTS);
        }
        // 2. èŽ·å–é‡‡è´­è®¢å•æ˜Žç»†
        // 3. èŽ·å–é‡‡è´­è®¢å•æ˜Žç»†
        List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi.getPurchaseOrderItemList(purchaseOrderId).getCheckedData();
        if (CollUtil.isEmpty(items)) {
            throw exception(WM_PURCHASE_ORDER_NO_ITEM);
        }
        // 3. ç”Ÿæˆåˆ°è´§é€šçŸ¥å•编码(使用编码规则服务)
        // 4. ç”Ÿæˆåˆ°è´§é€šçŸ¥å•编码
        String code = generateArrivalNoticeCode();
        // 4. åˆ›å»ºåˆ°è´§é€šçŸ¥å•主表
        // 5. åˆ›å»ºåˆ°è´§é€šçŸ¥å•主表,直接设为待入库状态
        MesWmArrivalNoticeDO notice = new MesWmArrivalNoticeDO();
        notice.setCode(code);
        notice.setName(purchaseOrder.getNo() + "到货通知");
        notice.setPurchaseOrderId(purchaseOrderId);
        notice.setPurchaseOrderCode(purchaseOrder.getNo());
        notice.setVendorId(purchaseOrder.getSupplierId());
        notice.setStatus(MesWmArrivalNoticeStatusEnum.PREPARE.getStatus());
        notice.setArrivalDate(java.time.LocalDateTime.now()); // åˆ°è´§æ—¶é—´ä¸ºå½“前时间
        notice.setStatus(MesWmArrivalNoticeStatusEnum.PENDING_RECEIPT.getStatus());
        arrivalNoticeMapper.insert(notice);
        // 5. éåŽ†æ˜Žç»†åˆ›å»ºåˆ°è´§é€šçŸ¥å•è¡Œ
        // 6. éåŽ†æ˜Žç»†åˆ›å»ºåˆ°è´§é€šçŸ¥å•è¡Œ
        boolean hasNeedQc = false;
        for (ErpPurchaseOrderItemRespDTO item : items) {
            MesWmArrivalNoticeLineDO line = new MesWmArrivalNoticeLineDO();
            line.setNoticeId(notice.getId());
            line.setPurchaseOrderItemId(item.getId());
            // æ³¨æ„ï¼šè¿™é‡Œéœ€è¦æ ¹æ® ERP äº§å“ ID æ˜ å°„到 MES ç‰©æ–™ ID
            // å®žé™…项目中需要通过映射服务处理,这里暂时留空让用户手动选择
            line.setArrivalQuantity(item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO);
            line.setItemId(item.getProductId()); // ä»Žé‡‡è´­è®¢å•明细带入物料编号
            BigDecimal arrivalQuantity = item.getCount() != null ? item.getCount().subtract(item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO) : BigDecimal.ZERO;
            line.setArrivalQuantity(arrivalQuantity);
            // ä»Žé‡‡è´­è®¢å•明细带入质检标识
            line.setIqcCheckFlag(Boolean.TRUE.equals(item.getQcCheckFlag()));
            boolean needQc = Boolean.TRUE.equals(item.getQcCheckFlag());
            line.setIqcCheckFlag(needQc);
            // å¦‚果不需要质检,合格数量等于到货数量
            if (!needQc) {
                line.setQualifiedQuantity(arrivalQuantity);
            } else {
                hasNeedQc = true;
            }
            arrivalNoticeLineService.createArrivalNoticeLineFromPurchaseOrderItem(line);
        }
        // 7. å¦‚果有需要质检的行,更新状态为待质检并自动创建 IQC å•
        if (hasNeedQc) {
            arrivalNoticeMapper.updateById(new MesWmArrivalNoticeDO()
                    .setId(notice.getId()).setStatus(MesWmArrivalNoticeStatusEnum.PENDING_QC.getStatus()));
            // ä¸ºéœ€è¦è´¨æ£€çš„行自动创建 IQC æ£€éªŒå•
            List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(notice.getId());
            for (MesWmArrivalNoticeLineDO line : lines) {
                if (Boolean.TRUE.equals(line.getIqcCheckFlag())) {
                    Long iqcId = iqcService.createIqcFromArrivalNotice(
                            notice.getId(), line, notice.getCode(), notice.getVendorId());
                    arrivalNoticeLineService.updateArrivalNoticeLineIqcId(line.getId(), iqcId);
                    log.info("[generateArrivalNoticeFromPurchaseOrder][到货通知单({}) è¡Œ({}) è‡ªåŠ¨åˆ›å»º IQC æ£€éªŒå•({})]",
                            notice.getId(), line.getId(), iqcId);
                }
            }
        }
        return notice.getId();
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptDetailServiceImpl.java
@@ -11,6 +11,7 @@
import jakarta.annotation.Resource;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.validation.annotation.Validated;
import java.math.BigDecimal;
@@ -42,6 +43,7 @@
    private MesWmMaterialStockService materialStockService;
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long createItemReceiptDetail(MesWmItemReceiptDetailSaveReqVO createReqVO) {
        // 1. æ ¡éªŒæ•°æ®
        itemReceiptService.validateItemReceiptEditable(createReqVO.getReceiptId());
@@ -57,6 +59,7 @@
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateItemReceiptDetail(MesWmItemReceiptDetailSaveReqVO updateReqVO) {
        // 1.1 æ ¡éªŒå­˜åœ¨
        MesWmItemReceiptDetailDO detail = validateItemReceiptDetailExists(updateReqVO.getId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/itemreceipt/MesWmItemReceiptServiceImpl.java
@@ -17,7 +17,6 @@
import cn.iocoder.yudao.module.mes.enums.wm.MesWmItemReceiptStatusEnum;
import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.mes.service.md.vendor.MesMdVendorService;
import cn.iocoder.yudao.module.mes.service.qc.iqc.MesQcIqcService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeService;
import cn.iocoder.yudao.module.mes.service.wm.arrivalnotice.MesWmArrivalNoticeLineService;
@@ -27,6 +26,9 @@
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
import cn.iocoder.yudao.module.erp.api.purchase.ErpSupplierApi;
import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import jakarta.annotation.Resource;
@@ -39,6 +41,7 @@
import java.math.BigDecimal;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -63,7 +66,8 @@
    @Lazy
    private MesWmArrivalNoticeService arrivalNoticeService;
    @Resource
    private MesMdVendorService vendorService;
    @Lazy
    private ErpSupplierApi supplierApi;
    @Resource
    @Lazy
    private MesQcIqcService iqcService;
@@ -78,6 +82,9 @@
    @Resource
    private ErpPurchaseOrderApi purchaseOrderApi;
    @Resource
    private MdmItemApi mdmItemApi;
    @Override
    public Long createItemReceipt(MesWmItemReceiptSaveReqVO createReqVO) {
@@ -106,8 +113,8 @@
    private void validateItemReceiptSaveData(MesWmItemReceiptSaveReqVO reqVO) {
        // æ ¡éªŒç¼–码唯一
        validateCodeUnique(reqVO.getId(), reqVO.getCode());
        // æ ¡éªŒä¾›åº”商存在
        vendorService.validateVendorExistsAndEnable(reqVO.getVendorId());
        // æ ¡éªŒä¾›åº”商存在(采购入库使用 ERP ä¾›åº”商)
        supplierApi.validateSupplier(reqVO.getVendorId());
        // æ ¡éªŒåˆ°è´§é€šçŸ¥å•存在
        if (reqVO.getNoticeId() != null) {
            MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(reqVO.getNoticeId());
@@ -155,9 +162,33 @@
            throw exception(WM_ITEM_RECEIPT_NO_LINE);
        }
        // æ ¡éªŒç‰©æ–™æ‰¹æ¬¡ç®¡ç†ï¼šå¦‚果物料开启批次管理,则明细的批次号不能为空
        validateBatchManagement(lines);
        // æäº¤ï¼ˆè‰ç¨¿ â†’ å¾…上架)
        itemReceiptMapper.updateById(new MesWmItemReceiptDO()
                .setId(id).setStatus(MesWmItemReceiptStatusEnum.APPROVING.getStatus()));
    }
    /**
     * æ ¡éªŒç‰©æ–™æ‰¹æ¬¡ç®¡ç†ï¼šå¦‚果物料开启批次管理,则行的批次号不能为空
     */
    private void validateBatchManagement(List<MesWmItemReceiptLineDO> lines) {
        // èŽ·å–æ‰€æœ‰ç‰©æ–™ID
        List<Long> itemIds = CollectionUtils.convertList(lines, MesWmItemReceiptLineDO::getItemId);
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds);
        // éåŽ†æ¯ä¸€è¡Œï¼Œæ£€æŸ¥ç‰©æ–™æ˜¯å¦å¼€å¯æ‰¹æ¬¡ç®¡ç†
        for (MesWmItemReceiptLineDO line : lines) {
            MdmItemRespDTO item = itemMap.get(line.getItemId());
            if (item == null) {
                continue;
            }
            // å¦‚果物料开启批次管理,检查行上的批次号是否为空
            if (Boolean.TRUE.equals(item.getIsBatchManaged()) && line.getBatchId() == null) {
                throw exception(WM_ITEM_RECEIPT_BATCH_REQUIRED, item.getCode(), item.getName());
            }
        }
    }
    @Override
@@ -245,14 +276,26 @@
    private void createTransactionList(MesWmItemReceiptDO receipt) {
        List<MesWmItemReceiptDetailDO> details = itemReceiptDetailService.getItemReceiptDetailListByReceiptId(receipt.getId());
        wmTransactionService.createTransactionList(convertList(details, detail -> new MesWmTransactionSaveReqDTO()
                .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
                .setQuantity(detail.getQuantity()) // å…¥åº“数量为正数
                .setBatchId(detail.getBatchId())
                .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
                .setVendorId(receipt.getVendorId()).setReceiptTime(receipt.getReceiptDate())
                .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN).setBizId(receipt.getId())
                .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId())));
        // èŽ·å–æ‰€æœ‰è¡Œä¿¡æ¯ï¼Œç”¨äºŽèŽ·å–æ‰¹æ¬¡å·
        List<MesWmItemReceiptLineDO> lines = itemReceiptLineService.getItemReceiptLineListByReceiptId(receipt.getId());
        Map<Long, MesWmItemReceiptLineDO> lineMap = CollectionUtils.convertMap(lines, MesWmItemReceiptLineDO::getId);
        wmTransactionService.createTransactionList(convertList(details, detail -> {
            // ä»Žè¡Œä¸ŠèŽ·å–æ‰¹æ¬¡ä¿¡æ¯ï¼ˆå¦‚æžœæ˜Žç»†æ²¡æœ‰æ‰¹æ¬¡å·ï¼‰
            MesWmItemReceiptLineDO line = lineMap.get(detail.getLineId());
            Long batchId = detail.getBatchId() != null ? detail.getBatchId() :
                    (line != null ? line.getBatchId() : null);
            String batchCode = line != null ? line.getBatchCode() : null;
            return new MesWmTransactionSaveReqDTO()
                    .setType(MesWmTransactionTypeEnum.IN.getType()).setItemId(detail.getItemId())
                    .setQuantity(detail.getQuantity())
                    .setBatchId(batchId).setBatchCode(batchCode)
                    .setWarehouseId(detail.getWarehouseId()).setLocationId(detail.getLocationId()).setAreaId(detail.getAreaId())
                    .setVendorId(receipt.getVendorId()).setReceiptTime(receipt.getReceiptDate())
                    .setBizType(MesBizTypeConstants.WM_ITEM_RECEIPT_IN).setBizId(receipt.getId())
                    .setBizCode(receipt.getCode()).setBizLineId(detail.getLineId());
        }));
    }
    @Override
@@ -331,26 +374,33 @@
        // 1. æ ¡éªŒåˆ°è´§é€šçŸ¥å•存在且状态为待入库
        MesWmArrivalNoticeDO notice = arrivalNoticeService.validateArrivalNoticeReadyForReceipt(arrivalNoticeId);
        // 2. èŽ·å–åˆ°è´§é€šçŸ¥å•è¡Œ
        // 2. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè¿‡å…¥åº“单(防止重复生成)
        MesWmItemReceiptDO existReceipt = itemReceiptMapper.selectByNoticeId(arrivalNoticeId);
        if (existReceipt != null) {
            throw exception(WM_ITEM_RECEIPT_ALREADY_EXISTS);
        }
        // 3. èŽ·å–åˆ°è´§é€šçŸ¥å•è¡Œ
        List<MesWmArrivalNoticeLineDO> lines = arrivalNoticeLineService.getArrivalNoticeLineListByNoticeId(arrivalNoticeId);
        if (CollUtil.isEmpty(lines)) {
            throw exception(WM_ARRIVAL_NOTICE_NO_LINE);
        }
        // 3. ç”Ÿæˆå…¥åº“单编码
        // 4. ç”Ÿæˆå…¥åº“单编码
        String code = generateItemReceiptCode();
        // 4. åˆ›å»ºå…¥åº“单主表
        // 5. åˆ›å»ºå…¥åº“单主表
        MesWmItemReceiptDO receipt = new MesWmItemReceiptDO();
        receipt.setCode(code);
        receipt.setName(notice.getName() + "入库单");
        receipt.setNoticeId(arrivalNoticeId);
        receipt.setPurchaseOrderCode(notice.getPurchaseOrderCode());
        receipt.setVendorId(notice.getVendorId());
        receipt.setReceiptDate(java.time.LocalDateTime.now()); // å…¥åº“时间为当前时间
        receipt.setStatus(MesWmItemReceiptStatusEnum.PREPARE.getStatus());
        itemReceiptMapper.insert(receipt);
        // 5. éåŽ†è¡Œåˆ›å»ºå…¥åº“å•è¡Œ
        // 6. éåŽ†è¡Œåˆ›å»ºå…¥åº“å•è¡Œ
        for (MesWmArrivalNoticeLineDO noticeLine : lines) {
            MesWmItemReceiptLineDO receiptLine = new MesWmItemReceiptLineDO();
            receiptLine.setReceiptId(receipt.getId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/productsales/MesWmProductSalesLineServiceImpl.java
@@ -204,7 +204,7 @@
    @Transactional(rollbackFor = Exception.class)
    public void updateProductSalesLineWhenOqcFinish(Long id, Long oqcId, Integer checkResult) {
        // 1. æ›´æ–°è¡Œçš„ oqcId + qualityStatus
        Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.PASS.getType())
        Integer qualityStatus = Objects.equals(checkResult, MesQcCheckResultEnum.QUALIFIED.getType())
                ? MesWmQualityStatusEnum.PASS.getStatus() : MesWmQualityStatusEnum.FAIL.getStatus();
        productSalesLineMapper.updateById(new MesWmProductSalesLineDO()
                .setId(id).setOqcId(oqcId).setQualityStatus(qualityStatus));
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/salesnotice/MesWmSalesNoticeServiceImpl.java
@@ -183,22 +183,28 @@
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Long generateSalesNoticeFromSaleOrder(Long saleOrderId) {
        // 1. èŽ·å–é”€å”®è®¢å•ä¿¡æ¯
        // 1. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè¿‡å‘货通知单(防止重复生成)
        MesWmSalesNoticeDO existNotice = salesNoticeMapper.selectBySaleOrderId(saleOrderId);
        if (existNotice != null) {
            throw exception(WM_SALES_NOTICE_ALREADY_EXISTS);
        }
        // 2. èŽ·å–é”€å”®è®¢å•ä¿¡æ¯
        ErpSaleOrderRespDTO saleOrder = saleOrderApi.getSaleOrder(saleOrderId).getCheckedData();
        if (saleOrder == null) {
            throw exception(WM_SALES_NOTICE_SALE_ORDER_NOT_EXISTS);
        }
        // 2. èŽ·å–é”€å”®è®¢å•æ˜Žç»†
        // 3. èŽ·å–é”€å”®è®¢å•æ˜Žç»†
        List<ErpSaleOrderItemRespDTO> saleOrderItems = saleOrderApi.getSaleOrderItemList(saleOrderId).getCheckedData();
        if (CollUtil.isEmpty(saleOrderItems)) {
            throw exception(WM_SALES_NOTICE_SALE_ORDER_ITEM_EMPTY);
        }
        // 3. ç”Ÿæˆå‘货通知单编码
        // 4. ç”Ÿæˆå‘货通知单编码
        String code = autoCodeRecordService.generateAutoCode("SALESNOTICE_CODE", null);
        // 4. åˆ›å»ºå‘货通知单主表
        // 5. åˆ›å»ºå‘货通知单主表
        MesWmSalesNoticeDO notice = new MesWmSalesNoticeDO();
        notice.setCode(code);
        notice.setName("销售订单[" + saleOrder.getNo() + "]发货通知");
@@ -209,7 +215,7 @@
        notice.setStatus(PREPARE.getStatus());
        salesNoticeMapper.insert(notice);
        // 5. éåŽ†æ˜Žç»†åˆ›å»ºå‘è´§é€šçŸ¥å•è¡Œ
        // 6. éåŽ†æ˜Žç»†åˆ›å»ºå‘è´§é€šçŸ¥å•è¡Œ
        for (ErpSaleOrderItemRespDTO saleOrderItem : saleOrderItems) {
            MesWmSalesNoticeLineDO line = new MesWmSalesNoticeLineDO();
            line.setNoticeId(notice.getId());
@@ -315,16 +321,21 @@
            throw exception(WM_SALES_NOTICE_STATUS_NOT_APPROVED);
        }
        // 2. èŽ·å–å‘è´§é€šçŸ¥å•è¡Œ
        // 2. æ ¡éªŒæ˜¯å¦å·²ç”Ÿæˆè¿‡é”€å”®å‡ºåº“单(防止重复生成)
        if (productSalesMapper.selectByNoticeId(noticeId) != null) {
            throw exception(WM_PRODUCT_SALES_ALREADY_EXISTS);
        }
        // 3. èŽ·å–å‘è´§é€šçŸ¥å•è¡Œ
        List<MesWmSalesNoticeLineDO> noticeLines = salesNoticeLineService.getSalesNoticeLineListByNoticeId(noticeId);
        if (CollUtil.isEmpty(noticeLines)) {
            throw exception(WM_SALES_NOTICE_LINE_EMPTY);
        }
        // 3. ç”Ÿæˆé”€å”®å‡ºåº“单编码
        // 4. ç”Ÿæˆé”€å”®å‡ºåº“单编码
        String code = autoCodeRecordService.generateAutoCode("WM_PRODUCT_SALES_CODE", null);
        // 4. åˆ›å»ºé”€å”®å‡ºåº“单主表
        // 5. åˆ›å»ºé”€å”®å‡ºåº“单主表
        MesWmProductSalesDO productSales = new MesWmProductSalesDO();
        productSales.setCode(code);
        productSales.setName("发货通知单[" + notice.getCode() + "]出库");
@@ -340,7 +351,7 @@
        productSales.setStatus(MesWmProductSalesStatusEnum.PREPARE.getStatus());
        productSalesMapper.insert(productSales);
        // 5. éåŽ†å‘è´§é€šçŸ¥å•è¡Œåˆ›å»ºé”€å”®å‡ºåº“å•è¡Œ
        // 6. éåŽ†å‘è´§é€šçŸ¥å•è¡Œåˆ›å»ºé”€å”®å‡ºåº“å•è¡Œ
        for (MesWmSalesNoticeLineDO noticeLine : noticeLines) {
            MesWmProductSalesLineDO salesLine = new MesWmProductSalesLineDO();
            salesLine.setSalesId(productSales.getId());
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsInboundStatusListener.java
@@ -4,7 +4,6 @@
import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
@@ -14,29 +13,38 @@
 */
@Component
@Slf4j
public class WmsInboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
public class WmsInboundStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
    /**
     * é‡‡è´­å…¥åº“审批流程定义 Key
     */
    private static final String PROCESS_DEFINITION_KEY = "wms_inbound_approve";
    @Resource
    private WmsApprovalService approvalService;
    @Override
    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
        // é€šè¿‡ businessKey æŸ¥è¯¢å…¥åº“单
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long orderId;
        try {
            orderId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            return; // businessKey ä¸æ˜¯æ•°å­—,忽略
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„æ•°å­—", event.getBusinessKey());
            return;
        }
        log.info("[onApplicationEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
                event.getBusinessKey(), event.getStatus());
        // è°ƒç”¨å®¡æ‰¹æœåŠ¡æ›´æ–°çŠ¶æ€
        try {
            approvalService.updateAuditStatus(orderId, "INBOUND", event.getStatus());
        } catch (Exception e) {
            log.error("[onApplicationEvent] æ›´æ–°å…¥åº“单审批状态失败,orderId={}", orderId, e);
            log.error("[onEvent] æ›´æ–°å…¥åº“单审批状态失败,orderId={}", orderId, e);
        }
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsOutboundStatusListener.java
@@ -4,7 +4,6 @@
import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
@@ -14,29 +13,38 @@
 */
@Component
@Slf4j
public class WmsOutboundStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
public class WmsOutboundStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
    /**
     * é”€å”®å‡ºåº“审批流程定义 Key
     */
    private static final String PROCESS_DEFINITION_KEY = "wms_outbound_approve";
    @Resource
    private WmsApprovalService approvalService;
    @Override
    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
        // é€šè¿‡ businessKey æŸ¥è¯¢å‡ºåº“单
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long orderId;
        try {
            orderId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            return; // businessKey ä¸æ˜¯æ•°å­—,忽略
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„æ•°å­—", event.getBusinessKey());
            return;
        }
        log.info("[onApplicationEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
                event.getBusinessKey(), event.getStatus());
        // è°ƒç”¨å®¡æ‰¹æœåŠ¡æ›´æ–°çŠ¶æ€
        try {
            approvalService.updateAuditStatus(orderId, "OUTBOUND", event.getStatus());
        } catch (Exception e) {
            log.error("[onApplicationEvent] æ›´æ–°å‡ºåº“单审批状态失败,orderId={}", orderId, e);
            log.error("[onEvent] æ›´æ–°å‡ºåº“单审批状态失败,orderId={}", orderId, e);
        }
    }
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wms/approval/listener/WmsTransferStatusListener.java
@@ -4,7 +4,6 @@
import cn.iocoder.yudao.module.mes.service.wms.approval.WmsApprovalService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationListener;
import org.springframework.stereotype.Component;
/**
@@ -14,29 +13,38 @@
 */
@Component
@Slf4j
public class WmsTransferStatusListener implements ApplicationListener<BpmProcessInstanceStatusEvent> {
public class WmsTransferStatusListener extends cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener {
    /**
     * è°ƒæ‹¨å®¡æ‰¹æµç¨‹å®šä¹‰ Key
     */
    private static final String PROCESS_DEFINITION_KEY = "wms_transfer_approve";
    @Resource
    private WmsApprovalService approvalService;
    @Override
    public void onApplicationEvent(BpmProcessInstanceStatusEvent event) {
        // é€šè¿‡ businessKey æŸ¥è¯¢è°ƒæ‹¨å•
    protected String getProcessDefinitionKey() {
        return PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long orderId;
        try {
            orderId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            return; // businessKey ä¸æ˜¯æ•°å­—,忽略
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„æ•°å­—", event.getBusinessKey());
            return;
        }
        log.info("[onApplicationEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,businessKey={}, status={}",
                event.getBusinessKey(), event.getStatus());
        // è°ƒç”¨å®¡æ‰¹æœåŠ¡æ›´æ–°çŠ¶æ€
        try {
            approvalService.updateAuditStatus(orderId, "TRANSFER", event.getStatus());
        } catch (Exception e) {
            log.error("[onApplicationEvent] æ›´æ–°è°ƒæ‹¨å•审批状态失败,orderId={}", orderId, e);
            log.error("[onEvent] æ›´æ–°è°ƒæ‹¨å•审批状态失败,orderId={}", orderId, e);
        }
    }