8 天以前 7ac5fb62d3ac852ec9505eb5c1999064aafee85f
feat(mes): 添加MES模块配置和功能

- 新增application-yzfx.yaml配置文件,包含数据库、Redis、消息队列等完整配置
- 添加设计项目审批流程功能,支持提交审批和流程定义查询
- 新增批次采购来源、销售去向和工序流转追踪功能
- 添加审批中状态枚举和相关错误码常量
- 集成BPM工作流,支持设计项目审批状态更新
已修改13个文件
已添加7个文件
1371 ■■■■■ 文件已修改
docs/batch_trace_extended_frontend.md 305 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/pd_project_bpm_approval_frontend.md 87 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java 17 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java 97 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchProcessTraceRespVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchPurchaseSourceRespVO.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java 10 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java 22 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java 121 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java 47 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java 35 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java 43 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml 65 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
yudao-server/src/main/resources/application-yzfx.yaml 324 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
docs/batch_trace_extended_frontend.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,305 @@
# æ‰¹æ¬¡è¿½æº¯æ‰©å±• - å‰ç«¯è”调方案
## æ¦‚è¿°
在现有批次追溯(向前/向后)基础上,扩展 4 ä¸ªèƒ½åŠ›ï¼Œè¡¥é½æ‰¹æ¬¡å…¨é“¾è·¯è¿½æº¯ï¼š
1. **采购来源**:从批次反查采购入库单 + IQC è´¨æ£€ç»“论(原料侧断链打通)
2. **销售去向**:从批次反查销售出库单 + OQC è´¨æ£€ç»“论(成品侧断链打通)
3. **工序流转**:从批次查看产出/投入的工序级事件(无需建表,动态聚合)
4. **SN åæŸ¥**:输入 SN åºåˆ—号直接定位批次并跳转详情
后端 4 ä¸ªæ–°æŽ¥å£å‡å·²å®Œæˆå¹¶ç¼–译通过,本文档指导前端对接。
---
## ä¸€ã€æ¶‰åŠé¡µé¢
| é¡µé¢ | è·¯å¾„ | æ”¹åŠ¨è¯´æ˜Ž |
|------|------|----------|
| æ‰¹æ¬¡è¿½æº¯é¡µ | `src/views/mes/qc/batchtrace/index.vue` | é¡¶éƒ¨æ–°å¢ž SN åæŸ¥è¾“入框 |
| è¿½æº¯è¯¦æƒ…弹窗 | `src/views/mes/qc/batchtrace/modules/trace-detail.vue` | å­é¡µç­¾æ‰©å±•为 5 ä¸ªï¼ˆç”Ÿäº§/采购来源/销售去向/工序流转/质检) |
| é‡‡è´­æ¥æºå­åˆ—表 | `src/views/mes/qc/batchtrace/modules/purchase-source-list.vue` | æ–°å¢žå­ç»„ä»¶ |
| é”€å”®åŽ»å‘å­åˆ—è¡¨ | `src/views/mes/qc/batchtrace/modules/sales-target-list.vue` | æ–°å¢žå­ç»„ä»¶ |
| å·¥åºæµè½¬å­åˆ—表 | `src/views/mes/qc/batchtrace/modules/process-trace-list.vue` | æ–°å¢žå­ç»„ä»¶ |
| æ‰¹æ¬¡ API | `src/api/mes/wm/batch/index.ts` | æ–°å¢ž 4 ä¸ªæŽ¥å£å‡½æ•° |
| åˆ—定义 | `src/views/mes/qc/batchtrace/data.ts` | æ–°å¢ž 3 ç»„列配置 |
---
## äºŒã€API æŽ¥å£
### 2.1 æ‰¹æ¬¡é‡‡è´­æ¥æº
**请求方式**:GET
**请求路径**:`/mes/wm/batch/purchase-sources`
**请求参数**:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | æ˜¯ | æ‰¹æ¬¡ç¼–码 |
**响应**:
```json
{
  "code": 0,
  "data": [
    {
      "receiptCode": "IR20250713001",
      "receiptDate": "2026-08-01 10:00:00",
      "purchaseOrderCode": "PO20250701001",
      "vendorId": 20,
      "vendorCode": "VENDOR001",
      "vendorName": "供应商A",
      "iqcCode": "IQC20250713001",
      "checkResult": 1,
      "itemId": 100,
      "itemCode": "ITEM001",
      "itemName": "原料A",
      "batchCode": "BT20260731001",
      "receivedQuantity": 100
    }
  ]
}
```
**字段说明**:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| receiptCode | String | é‡‡è´­å…¥åº“单号 |
| receiptDate | Date | å…¥åº“日期 |
| purchaseOrderCode | String | é‡‡è´­è®¢å•号 |
| vendorCode / vendorName | String | ä¾›åº”商编码/名称(后端富化) |
| iqcCode | String | IQC è´¨æ£€å•号(可能为空) |
| checkResult | Integer | IQC è´¨æ£€ç»“论,字典 `mes_qc_check_result` |
| itemCode / itemName | String | ç‰©æ–™ç¼–码/名称(后端富化) |
| batchCode | String | æ‰¹æ¬¡ç¼–码 |
| receivedQuantity | BigDecimal | å…¥åº“数量 |
### 2.2 æ‰¹æ¬¡é”€å”®å޻向
**请求方式**:GET
**请求路径**:`/mes/wm/batch/sales-targets`
**请求参数**:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | æ˜¯ | æ‰¹æ¬¡ç¼–码 |
**响应**:
```json
{
  "code": 0,
  "data": [
    {
      "salesCode": "SS20250713001",
      "salesDate": "2026-08-05 14:00:00",
      "salesOrderCode": "SO20250701001",
      "clientId": 30,
      "clientName": "客户A",
      "oqcCode": "OQC20250713001",
      "checkResult": 1,
      "itemId": 100,
      "itemCode": "ITEM001",
      "itemName": "成品A",
      "batchCode": "BT20260804001",
      "quantity": 50,
      "qualityStatus": 1
    }
  ]
}
```
**字段说明**:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| salesCode | String | é”€å”®å‡ºåº“单号 |
| salesDate | Date | å‡ºåº“日期 |
| salesOrderCode | String | é”€å”®è®¢å•号 |
| clientName | String | å®¢æˆ·åç§°ï¼ˆåŽç«¯å¯ŒåŒ–,注意:客户无编码字段) |
| oqcCode | String | OQC è´¨æ£€å•号(可能为空) |
| checkResult | Integer | OQC è´¨æ£€ç»“论,字典 `mes_qc_check_result` |
| itemCode / itemName | String | ç‰©æ–™ç¼–码/名称(后端富化) |
| batchCode | String | æ‰¹æ¬¡ç¼–码 |
| quantity | BigDecimal | å‡ºåº“数量 |
| qualityStatus | Integer | è´¨é‡çŠ¶æ€ï¼Œå­—å…¸ `mes_wm_quality_status` |
### 2.3 æ‰¹æ¬¡å·¥åºæµè½¬
**请求方式**:GET
**请求路径**:`/mes/wm/batch/process-trace`
**请求参数**:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| code | String | æ˜¯ | æ‰¹æ¬¡ç¼–码 |
**响应**:
```json
{
  "code": 0,
  "data": [
    {
      "eventType": "IN",
      "workOrderId": 50,
      "workOrderCode": "WO20260730001",
      "processId": 10,
      "processCode": "P001",
      "processName": "下料",
      "batchCode": "BT20260804001",
      "quantity": 100,
      "qualityStatus": null,
      "eventTime": "2026-08-04 09:00:00",
      "processSort": 1
    },
    {
      "eventType": "OUT",
      "workOrderId": 50,
      "workOrderCode": "WO20260730001",
      "processId": 10,
      "processCode": "P001",
      "processName": "下料",
      "batchCode": "BT20260804001",
      "quantity": 90,
      "qualityStatus": 1,
      "eventTime": "2026-08-04 10:00:00",
      "processSort": 1
    }
  ]
}
```
**字段说明**:
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| eventType | String | äº‹ä»¶ç±»åž‹ï¼š`OUT` = äº§å‡ºã€`IN` = æŠ•å…¥ |
| workOrderCode | String | å·¥å•号(后端富化) |
| processCode / processName | String | å·¥åºç¼–码/名称(后端富化) |
| batchCode | String | æ‰¹æ¬¡ç¼–码 |
| quantity | BigDecimal | æ•°é‡ï¼ˆOUT=产出数量,IN=投入数量) |
| qualityStatus | Integer | è´¨é‡çŠ¶æ€ï¼ˆä»… OUT ä¾§æœ‰å€¼ï¼ŒIN ä¾§ä¸º null) |
| eventTime | Date | äº‹ä»¶æ—¶é—´ï¼ˆOUT=产出日期,IN=消耗日期) |
| processSort | Integer | å·¥åºåºå·ï¼ˆç”¨äºŽå±•示工序先后) |
**排序规则**:后端已按 `workOrderId asc â†’ processSort asc(空值置后)→ eventTime asc` æŽ’序,前端直接按返回顺序展示即可。
### 2.4 SN åæŸ¥æ‰¹æ¬¡
**请求方式**:GET
**请求路径**:`/mes/wm/batch/get-by-sn`
**请求参数**:
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| sn | String | æ˜¯ | SN åºåˆ—号 |
**响应**:与 `GET /mes/wm/batch/get` å®Œå…¨ä¸€è‡´ï¼ˆ`MesWmBatchRespVO`),未命中返回 `data: null`:
```json
{
  "code": 0,
  "data": {
    "id": 1,
    "code": "BT20260804001",
    "itemId": 100,
    "itemCode": "ITEM001",
    "itemName": "成品A",
    "vendorCode": "VENDOR001",
    "vendorName": "供应商A",
    "clientName": "客户A",
    "workOrderCode": "WO20260730001",
    "taskCode": "TASK001",
    "workstationCode": "WS001",
    "toolCode": "TOOL001",
    "lotNumber": "LOT20260801"
  }
}
```
**说明**:SN åœ¨ `mes_wm_sn` è¡¨é€šè¿‡ `code` å”¯ä¸€åŒ¹é…ï¼Œå‘½ä¸­åŽå–å…¶ `batch_code` åæŸ¥æ‰¹æ¬¡ã€‚前端根据 `data` æ˜¯å¦ä¸ºç©ºåˆ¤æ–­æ˜¯å¦æç¤º"未找到对应批次"。
---
## ä¸‰ã€å­—段展示规则
| åŠŸèƒ½ | å±•示位置 | å­—段说明 |
|------|----------|----------|
| é‡‡è´­æ¥æº | è¯¦æƒ…弹窗子页签"采购来源" | å…¥åº“单号、入库日期、采购订单、供应商、IQC单号、IQC结论、物料、数量 |
| é”€å”®å޻向 | è¯¦æƒ…弹窗子页签"销售去向" | å‡ºåº“单号、出库日期、销售订单、客户、OQC单号、OQC结论、物料、数量、质量状态 |
| å·¥åºæµè½¬ | è¯¦æƒ…弹窗子页签"工序流转" | äº‹ä»¶ç±»åž‹ã€å·¥å•号、工序编码/名称、数量、时间、质量状态 |
| SN åæŸ¥ | è¿½æº¯é¡µé¡¶éƒ¨è¾“入框 | è¾“å…¥ SN â†’ å‘½ä¸­ç›´æŽ¥æ‰“开批次详情弹窗 |
**字典说明**:
| å­—å…¸ | å–值 |
|------|------|
| `mes_qc_check_result` | 1=合格、2=特采、3=退货、4=报废 |
| `mes_wm_quality_status` | 0=待检、1=合格、2=不合格 |
---
## å››ã€æ•°æ®å¸¦å…¥è¯´æ˜Ž
| åœºæ™¯ | å¸¦å…¥è¯´æ˜Ž |
|------|----------|
| è¿½æº¯é¡µ â†’ é‡‡è´­æ¥æº | ç‚¹å‡»æ‰¹æ¬¡è¡Œï¼Œæºå¸¦è¯¥è¡Œçš„ `code` è°ƒç”¨ `getPurchaseSourceList(code)` |
| è¿½æº¯é¡µ â†’ é”€å”®å޻向 | ç‚¹å‡»æ‰¹æ¬¡è¡Œï¼Œæºå¸¦è¯¥è¡Œçš„ `code` è°ƒç”¨ `getSalesTargetList(code)` |
| è¿½æº¯é¡µ â†’ å·¥åºæµè½¬ | ç‚¹å‡»æ‰¹æ¬¡è¡Œï¼Œæºå¸¦è¯¥è¡Œçš„ `code` è°ƒç”¨ `getProcessTraceList(code)` |
| SN è¾“入框 â†’ è¯¦æƒ… | è¾“å…¥ SN è°ƒç”¨ `getBatchBySn(sn)`,返回的 `code` ä½œä¸ºè¯¦æƒ…弹窗查询依据(或直接使用返回数据) |
---
## äº”、接口调用时序
1. è¿½æº¯é¡µåŠ è½½æ‰¹æ¬¡åˆ—è¡¨ï¼ˆ`/mes/wm/batch/page`)
2. ç‚¹å‡»æŸæ‰¹æ¬¡è¡Œ â†’ æ‰“开详情弹窗,弹窗内默认展示"生产"页签(向前/向后追溯)
3. åˆ‡æ¢"采购来源/销售去向/工序流转"页签时,按需调用对应接口(建议懒加载:首次切换才请求)
4. é¡¶éƒ¨ SN è¾“入框 â†’ æŒ‰å›žè½¦æˆ–点击搜索 â†’ `getBatchBySn` â†’ å‘½ä¸­æ‰“开详情弹窗,未命中提示无记录
---
## å…­ã€ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| åŽŸæ–™æ‰¹æ¬¡ | æœ‰é‡‡è´­æ¥æºï¼ˆå…¥åº“+IQC),无销售去向;工序流转以 IN æŠ•入为主 |
| æˆå“æ‰¹æ¬¡ | æœ‰é”€å”®åŽ»å‘ï¼ˆå‡ºåº“+OQC),无采购来源;工序流转以 OUT äº§å‡ºä¸ºä¸» |
| ä¸­é—´æ‰¹æ¬¡ | åŒæ—¶å­˜åœ¨ IN ä¸Ž OUT äº‹ä»¶ï¼Œä½“现工序级流转 |
| è´¨æ£€å•缺失 | `iqcCode`/`oqcCode` å¯èƒ½ä¸ºç©ºï¼ˆå•据未关联),结论列展示"-" |
| SN æœªå‘½ä¸­ | `get-by-sn` è¿”回 `data: null`,前端提示"未找到该 SN å¯¹åº”的批次" |
| æ•°æ®ä¸ºç©º | ä¸‰ä¸ªå­åˆ—表接口均可能返回空数组,页面展示空态提示 |
---
## ä¸ƒã€æ³¨æ„äº‹é¡¹
- ä¸‰ä¸ªåˆ—表接口与既有 forward/backward ç›¸åŒï¼Œå‡éœ€è¦ `mes:wm-batch:query` æƒé™
- å­é¡µç­¾å»ºè®®æ‡’加载 + ç¼“存,避免每次切换重复请求
- å·¥åºæµè½¬çš„ `processSort` ç”±åŽç«¯æŽ’好序,前端勿再排序
- é”€å”®ä¾§ `qualityStatus` ä¸ºå­—典值,展示用 `mes_wm_quality_status` å­—典翻译
- æµ‹è¯•环境可能存在无 `batchCode` çš„销售出库行数据,属历史脏数据,前端对空字段做兜底展示即可
---
## å…«ã€ç›¸å…³æŽ¥å£æ±‡æ€»
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /mes/wm/batch/purchase-sources | æ‰¹æ¬¡é‡‡è´­æ¥æºï¼ˆå…¥åº“ + IQC) |
| GET | /mes/wm/batch/sales-targets | æ‰¹æ¬¡é”€å”®åŽ»å‘ï¼ˆå‡ºåº“ + OQC) |
| GET | /mes/wm/batch/process-trace | æ‰¹æ¬¡å·¥åºæµè½¬ï¼ˆäº§å‡º/投入) |
| GET | /mes/wm/batch/get-by-sn | SN åæŸ¥æ‰¹æ¬¡ |
docs/pd_project_bpm_approval_frontend.md
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,87 @@
# MES äº§å“é¡¹ç›®æŽ¥å…¥ BPM å®¡æ‰¹ - å‰ç«¯è”调方案
## æ¶‰åŠé¡µé¢
- ç”Ÿäº§ç®¡æŽ§-产品设计-产品项目 åˆ—表页(`src/views/mes/pd/project/index.vue`)
- äº§å“é¡¹ç›® è¯¦æƒ…页(`src/views/mes/pd/project/detail/index.vue`)
- äº§å“é¡¹ç›® æµç¨‹è¿›åº¦ç»„件(`src/views/mes/pd/project/modules/workflow-timeline.vue`)
## ä¸šåŠ¡æµç¨‹ä¸Žæ•°æ®å¸¦å…¥
1. å¾…审核(2) â†’ åˆ—表/详情点「提交审批」:先调 `GET /mes/pd/project/approve-process-list` èŽ·å–å¯ç”¨ BPM æµç¨‹ â†’ æ‰“开流程选择弹窗 â†’ é€‰ä¸­æµç¨‹åŽè°ƒ `PUT /mes/pd/project/submit-approval?id={id}&processDefinitionKey={key}` â†’ é¡¹ç›®çŠ¶æ€å˜ä¸º **审批中(5)**
2. BPM åŽå°å®¡æ‰¹é€šè¿‡ â†’ åŽç«¯ç›‘听器回调 â†’ é¡¹ç›®çŠ¶æ€å˜ä¸º **待归档(3)**;审批驳回/取消 â†’ çŠ¶æ€å›žåˆ° **待审核(2)**,可重新提交
3. å®¡æ‰¹ä¸­(5) â†’ åˆ—表/详情显示「查看审批」按钮 â†’ è·³è½¬ BPM æµç¨‹è¯¦æƒ…页查看审批进度
4. å¾…å½’æ¡£(3) â†’ ç‚¹ã€Œå½’档」→ `PUT /mes/pd/project/archive?id={id}` â†’ çŠ¶æ€å˜ä¸º **已归档(4)**
## API
| æ–¹æ³• | è·¯å¾„ | è¯´æ˜Ž |
|------|------|------|
| GET | /mes/pd/project/approve-process-list | èŽ·å¾—å¯ç”¨ BPM å®¡æ‰¹æµç¨‹åˆ—表 |
| PUT | /mes/pd/project/submit-approval | æäº¤å®¡æ‰¹ï¼ˆå¾…审核→审批中) |
| PUT | /mes/pd/project/archive | å½’档(待归档→已归档) |
**请求参数(submit-approval):**
| å‚æ•° | ç±»åž‹ | å¿…å¡« | è¯´æ˜Ž |
|------|------|------|------|
| id | Long | æ˜¯ | é¡¹ç›®ç¼–号 |
| processDefinitionKey | String | æ˜¯ | é€‰ä¸­çš„ BPM æµç¨‹å®šä¹‰ Key |
**响应字段变更(/get、/page):**
| å­—段 | ç±»åž‹ | è¯´æ˜Ž |
|------|------|------|
| processInstanceId | String | BPM æµç¨‹å®žä¾‹ç¼–号,审批中及以后有值 |
**响应示例(approve-process-list):**
```json
{
  "code": 0,
  "data": [
    { "id": "xxx", "name": "产品项目审批流程", "key": "mes-pd-project-audit" }
  ]
}
```
## å­—段展示规则
| å­—段 | å±•示位置 | è¯´æ˜Ž |
|------|----------|------|
| çŠ¶æ€ | åˆ—表、详情 | æ–°å¢žå­—典值「审批中」(value=5),列表 `CellDict` è‡ªåŠ¨æ¸²æŸ“ |
| processInstanceId | åˆ—表/详情「查看审批」 | æœ‰å€¼æ‰æ˜¾ç¤ºã€ŒæŸ¥çœ‹å®¡æ‰¹ã€æŒ‰é’® |
## é¡µé¢æ”¹åЍ
### 1. åˆ—表页 index.vue
- ã€Œå®¡æ ¸ã€æŒ‰é’®æ”¹ä¸ºã€Œæäº¤å®¡æ‰¹ã€ï¼š`ifShow` ä»ä¸º `status === PENDING_AUDIT`,点击先查流程列表(空则提示「暂无可用审批流程,请先配置 BPM æµç¨‹ã€ï¼‰ï¼Œæœ‰åˆ™æ‰“开流程选择弹窗
- æ–°å¢žã€ŒæŸ¥çœ‹å®¡æ‰¹ã€æŒ‰é’®ï¼š`ifShow` ä¸º `status === APPROVING` ä¸” `processInstanceId` æœ‰å€¼ï¼Œç‚¹å‡»è·³ BPM æµç¨‹è¯¦æƒ…
- æ–°å¢žæµç¨‹é€‰æ‹©å¼¹çª—组件 `process-select-modal.vue`(Select ä¸‹æ‹‰é€‰æµç¨‹ï¼Œé»˜è®¤é€‰ç¬¬ä¸€ä¸ªï¼Œç¡®è®¤åŽè°ƒ submit-approval)
### 2. è¯¦æƒ…页 detail/index.vue
- ã€Œå®¡æ ¸é€šè¿‡ã€æŒ‰é’®æ”¹ä¸ºã€Œæäº¤å®¡æ‰¹ã€ï¼Œé€»è¾‘同上,成功后刷新详情
- æ–°å¢žã€ŒæŸ¥çœ‹å®¡æ‰¹ã€æŒ‰é’®ï¼š`ifShow` ä¸ºå®¡æ‰¹ä¸­çŠ¶æ€ï¼Œç‚¹å‡»è·³ BPM æµç¨‹è¯¦æƒ…
- é¡¶éƒ¨æŒ‰é’®åŒºæ–°å¢žå®¡æ‰¹ä¸­åˆ¤æ–­
### 3. æµç¨‹è¿›åº¦ç»„ä»¶ workflow-timeline.vue
- æ­¥éª¤ç”± 5 æ­¥æ‰©ä¸º 6 æ­¥ï¼šå¾…发布 â†’ å¾…上传 â†’ å¾…审核 â†’ **审批中** â†’ å¾…å½’æ¡£ â†’ å·²å½’æ¡£
- çŠ¶æ€å€¼åˆ°æ­¥éª¤ç´¢å¼•çš„æ˜ å°„éœ€æ˜¾å¼å¤„ç†ï¼ˆå®¡æ‰¹ä¸­ value=5 æ’在待审核之后)
## ä¸šåŠ¡è§„åˆ™è¯´æ˜Ž
| åœºæ™¯ | è§„则 |
|------|------|
| æäº¤å®¡æ‰¹ | ä»…待审核(2)状态可提交;若项目下存在未审核的设计资料,后端返回错误「该项目下存在未审核的资料」 |
| å®¡æ‰¹é€šè¿‡ | çŠ¶æ€ â†’ å¾…å½’æ¡£(3),可执行归档 |
| å®¡æ‰¹é©³å›ž/取消 | çŠ¶æ€ â†’ å¾…审核(2),可修改后重新提交 |
| å½’æ¡£ | ä»…å¾…å½’æ¡£(3)状态可归档,归档后生成台账记录、项目不可再修改 |
## æ³¨æ„äº‹é¡¹
- éœ€å…ˆåœ¨ BPM åŽå°æ‰‹å·¥éƒ¨ç½²å®¡æ‰¹æµç¨‹ï¼ˆæµç¨‹ key=`mes-pd-project-audit`,分类=`mes_pd_project_approve`),否则「提交审批」提示暂无可用流程
- æœªæäº¤å®¡æ‰¹ï¼ˆprocessInstanceId ä¸ºç©ºï¼‰æ—¶ç‚¹ã€ŒæŸ¥çœ‹å®¡æ‰¹ã€åº”提示「该项目尚未提交审批」
- åŽ†å²æ•°æ® status 0-4 ä¸å—影响;时间线组件需兼容状态值到步骤的非连续映射
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
@@ -124,15 +124,24 @@
        return success(true);
    }
    @PutMapping("/audit")
    @Operation(summary = "审核设计项目(待审核→待归档)")
    @PutMapping("/submit-approval")
    @Operation(summary = "提交设计项目审批(待审核→审批中)")
    @Parameter(name = "id", description = "编号", required = true)
    @Parameter(name = "processDefinitionKey", description = "流程定义 Key", required = true)
    @PreAuthorize("@ss.hasPermission('mes:pd-project:update')")
    public CommonResult<Boolean> auditProject(@RequestParam("id") Long id) {
        projectService.auditProject(id);
    public CommonResult<Boolean> submitProjectApproval(@RequestParam("id") Long id,
                                                       @RequestParam("processDefinitionKey") String processDefinitionKey) {
        projectService.submitProjectApproval(id, processDefinitionKey);
        return success(true);
    }
    @GetMapping("/approve-process-list")
    @Operation(summary = "获得设计项目审批流程列表")
    @PreAuthorize("@ss.hasPermission('mes:pd-project:update')")
    public CommonResult<List<Map<String, Object>>> getProjectApproveProcessDefinitionList() {
        return success(projectService.getProjectApproveProcessDefinitionList());
    }
    @PutMapping("/archive")
    @Operation(summary = "归档设计项目(待归档→已归档),同时自动创建归档台账")
    @Parameter(name = "id", description = "编号", required = true)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java
@@ -66,6 +66,9 @@
    @ExcelProperty("备注")
    private String remark;
    @Schema(description = "BPM æµç¨‹å®žä¾‹ç¼–号")
    private String processInstanceId;
    @Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
    @ExcelProperty("创建时间")
    private LocalDateTime createTime;
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java
@@ -6,7 +6,10 @@
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
@@ -15,6 +18,7 @@
import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
import cn.iocoder.yudao.module.mes.dal.dataobject.md.workstation.MesMdWorkstationDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.task.MesProTaskDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.tm.tool.MesTmToolDO;
@@ -23,6 +27,7 @@
import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
import cn.iocoder.yudao.module.mes.service.md.workstation.MesMdWorkstationService;
import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
import cn.iocoder.yudao.module.mes.service.pro.task.MesProTaskService;
import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
import cn.iocoder.yudao.module.mes.service.tm.tool.MesTmToolService;
@@ -71,6 +76,8 @@
    private MesMdWorkstationService workstationService;
    @Resource
    private MesTmToolService toolService;
    @Resource
    private MesProProcessService processService;
    @GetMapping("/get")
    @Operation(summary = "获得批次详情")
@@ -105,6 +112,42 @@
    public CommonResult<List<MesWmBatchRespVO>> getBackwardList(@RequestParam("code") @Valid String code) {
        List<MesWmBatchDO> list = batchService.getBackwardBatchList(code);
        return success(buildBatchRespVOList(list));
    }
    @GetMapping("/purchase-sources")
    @Operation(summary = "批次采购来源(采购入库单 + IQC)")
    @Parameter(name = "code", description = "批次编码", required = true, example = "BATCH20250314001")
    @PreAuthorize("@ss.hasPermission('mes:wm-batch:query')")
    public CommonResult<List<MesWmBatchPurchaseSourceRespVO>> getPurchaseSourceList(@RequestParam("code") @Valid String code) {
        List<MesWmBatchPurchaseSourceRespVO> list = batchService.getPurchaseSourceList(code);
        return success(buildPurchaseSourceRespVOList(list));
    }
    @GetMapping("/sales-targets")
    @Operation(summary = "批次销售去向(销售出库单 + OQC)")
    @Parameter(name = "code", description = "批次编码", required = true, example = "BATCH20250314001")
    @PreAuthorize("@ss.hasPermission('mes:wm-batch:query')")
    public CommonResult<List<MesWmBatchSalesTargetRespVO>> getSalesTargetList(@RequestParam("code") @Valid String code) {
        List<MesWmBatchSalesTargetRespVO> list = batchService.getSalesTargetList(code);
        return success(buildSalesTargetRespVOList(list));
    }
    @GetMapping("/process-trace")
    @Operation(summary = "批次工序流转(产出/投入事件)")
    @Parameter(name = "code", description = "批次编码", required = true, example = "BATCH20250314001")
    @PreAuthorize("@ss.hasPermission('mes:wm-batch:query')")
    public CommonResult<List<MesWmBatchProcessTraceRespVO>> getProcessTraceList(@RequestParam("code") @Valid String code) {
        List<MesWmBatchProcessTraceRespVO> list = batchService.getProcessTraceList(code);
        return success(buildProcessTraceRespVOList(list));
    }
    @GetMapping("/get-by-sn")
    @Operation(summary = "根据 SN ç åæŸ¥æ‰¹æ¬¡")
    @Parameter(name = "sn", description = "SN åºåˆ—号", required = true, example = "SN20250314001")
    @PreAuthorize("@ss.hasPermission('mes:wm-batch:query')")
    public CommonResult<MesWmBatchRespVO> getBatchBySn(@RequestParam("sn") @Valid String sn) {
        MesWmBatchDO batch = batchService.getBatchBySn(sn);
        return success(buildBatchRespVO(batch));
    }
    // ==================== æ‹¼æŽ¥ VO ====================
@@ -162,4 +205,58 @@
        return buildBatchRespVOList(Collections.singletonList(batch)).get(0);
    }
    private List<MesWmBatchPurchaseSourceRespVO> buildPurchaseSourceRespVOList(List<MesWmBatchPurchaseSourceRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmBatchPurchaseSourceRespVO::getItemId));
        Map<Long, SrmSupplierRespDTO> vendorMap = srmSupplierApi.getSupplierList(
                convertSet(list, MesWmBatchPurchaseSourceRespVO::getVendorId)).getCheckedData().stream()
                .collect(Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
        list.forEach(vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName());
            });
            MapUtils.findAndThen(vendorMap, vo.getVendorId(),
                    vendor -> vo.setVendorCode(vendor.getCode()).setVendorName(vendor.getName()));
        });
        return list;
    }
    private List<MesWmBatchSalesTargetRespVO> buildSalesTargetRespVOList(List<MesWmBatchSalesTargetRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                convertSet(list, MesWmBatchSalesTargetRespVO::getItemId));
        Map<Long, CrmCustomerRespDTO> clientMap = customerApi.getCustomerMap(
                convertSet(list, MesWmBatchSalesTargetRespVO::getClientId));
        list.forEach(vo -> {
            MapUtils.findAndThen(itemMap, vo.getItemId(), item -> {
                vo.setItemCode(item.getCode()).setItemName(item.getName());
            });
            MapUtils.findAndThen(clientMap, vo.getClientId(),
                    client -> vo.setClientName(client.getName()));
        });
        return list;
    }
    private List<MesWmBatchProcessTraceRespVO> buildProcessTraceRespVOList(List<MesWmBatchProcessTraceRespVO> list) {
        if (CollUtil.isEmpty(list)) {
            return Collections.emptyList();
        }
        Map<Long, MesProWorkOrderDO> workOrderMap = workOrderService.getWorkOrderMap(
                convertSet(list, MesWmBatchProcessTraceRespVO::getWorkOrderId));
        Map<Long, MesProProcessDO> processMap = processService.getProcessMap(
                convertSet(list, MesWmBatchProcessTraceRespVO::getProcessId));
        list.forEach(vo -> {
            MapUtils.findAndThen(workOrderMap, vo.getWorkOrderId(),
                    workOrder -> vo.setWorkOrderCode(workOrder.getCode()));
            MapUtils.findAndThen(processMap, vo.getProcessId(),
                    process -> vo.setProcessCode(process.getCode()).setProcessName(process.getName()));
        });
        return list;
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchProcessTraceRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES æ‰¹æ¬¡å·¥åºæµè½¬ Response VO")
@Data
public class MesWmBatchProcessTraceRespVO {
    @Schema(description = "事件类型:IN-工序投入 / OUT-工序产出", example = "OUT")
    private String eventType;
    @Schema(description = "生产工单 ID", example = "1")
    private Long workOrderId;
    @Schema(description = "生产工单编号", example = "WO20250801001")
    private String workOrderCode;
    @Schema(description = "工序 ID", example = "1")
    private Long processId;
    @Schema(description = "工序编码", example = "PROC001")
    private String processCode;
    @Schema(description = "工序名称", example = "CNC åŠ å·¥")
    private String processName;
    @Schema(description = "批次号", example = "BATCH20250801001")
    private String batchCode;
    @Schema(description = "数量", example = "100")
    private BigDecimal quantity;
    @Schema(description = "质量状态(字典 mes_wm_quality_status),仅产出侧有值", example = "1")
    private Integer qualityStatus;
    @Schema(description = "事件时间")
    private LocalDateTime eventTime;
    @Schema(description = "工序序号(排序用)", example = "1")
    private Integer processSort;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchPurchaseSourceRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,52 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES æ‰¹æ¬¡é‡‡è´­æ¥æº Response VO")
@Data
public class MesWmBatchPurchaseSourceRespVO {
    @Schema(description = "采购入库单号", example = "IR20250731001")
    private String receiptCode;
    @Schema(description = "入库日期")
    private LocalDateTime receiptDate;
    @Schema(description = "采购订单编号", example = "PO20250731001")
    private String purchaseOrderCode;
    @Schema(description = "供应商 ID", example = "1")
    private Long vendorId;
    @Schema(description = "供应商编码", example = "V001")
    private String vendorCode;
    @Schema(description = "供应商名称", example = "供应商A")
    private String vendorName;
    @Schema(description = "来料检验单号(IQC)", example = "IQC20250731001")
    private String iqcCode;
    @Schema(description = "IQC æ£€éªŒç»“论(字典 mes_qc_check_result)", example = "1")
    private Integer checkResult;
    @Schema(description = "物料 ID", example = "1")
    private Long itemId;
    @Schema(description = "物料编码", example = "ITEM001")
    private String itemCode;
    @Schema(description = "物料名称", example = "产品A")
    private String itemName;
    @Schema(description = "批次号", example = "BATCH20250731001")
    private String batchCode;
    @Schema(description = "入库数量", example = "100")
    private BigDecimal receivedQuantity;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,55 @@
package cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.math.BigDecimal;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - MES æ‰¹æ¬¡é”€å”®å޻向 Response VO")
@Data
public class MesWmBatchSalesTargetRespVO {
    @Schema(description = "销售出库单号", example = "SO20250801001")
    private String salesCode;
    @Schema(description = "出库日期")
    private LocalDateTime salesDate;
    @Schema(description = "销售订单编号", example = "SO20250801001")
    private String salesOrderCode;
    @Schema(description = "客户 ID", example = "1")
    private Long clientId;
    @Schema(description = "客户编码", example = "C001")
    private String clientCode;
    @Schema(description = "客户名称", example = "客户A")
    private String clientName;
    @Schema(description = "出厂检验单号(OQC)", example = "OQC20250801001")
    private String oqcCode;
    @Schema(description = "OQC æ£€éªŒç»“论(字典 mes_qc_check_result)", example = "1")
    private Integer checkResult;
    @Schema(description = "物料 ID", example = "1")
    private Long itemId;
    @Schema(description = "物料编码", example = "ITEM001")
    private String itemCode;
    @Schema(description = "物料名称", example = "产品A")
    private String itemName;
    @Schema(description = "批次号", example = "BATCH20250801001")
    private String batchCode;
    @Schema(description = "出库数量", example = "100")
    private BigDecimal quantity;
    @Schema(description = "质量状态(字典 mes_wm_quality_status)", example = "1")
    private Integer qualityStatus;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java
@@ -58,6 +58,14 @@
     */
    private Integer status;
    /**
     * BPM æµç¨‹å®žä¾‹ç¼–号
     */
    private String processInstanceId;
    /**
     * BPM æµç¨‹å®šä¹‰ Key
     */
    private String processDefinitionKey;
    /**
     * ä¸»è®¾è®¡å¸ˆ
     *
     * å…³è” AdminUserDO#getId()
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
@@ -7,6 +7,9 @@
import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.apache.ibatis.annotations.Mapper;
@@ -185,4 +188,28 @@
     */
    List<MesWmBatchDO> selectListByBackward(String batchCode);
    /**
     * æŸ¥è¯¢æ‰¹æ¬¡é‡‡è´­æ¥æºï¼ˆé‡‡è´­å…¥åº“单 + IQC)
     *
     * @param batchCode æ‰¹æ¬¡å·
     * @return é‡‡è´­æ¥æºåˆ—表
     */
    List<MesWmBatchPurchaseSourceRespVO> selectPurchaseSourceList(String batchCode);
    /**
     * æŸ¥è¯¢æ‰¹æ¬¡é”€å”®åŽ»å‘ï¼ˆé”€å”®å‡ºåº“å• + OQC)
     *
     * @param batchCode æ‰¹æ¬¡å·
     * @return é”€å”®åŽ»å‘åˆ—è¡¨
     */
    List<MesWmBatchSalesTargetRespVO> selectSalesTargetList(String batchCode);
    /**
     * æŸ¥è¯¢æ‰¹æ¬¡å·¥åºæµè½¬ï¼ˆäº§å‡º/投入事件)
     *
     * @param batchCode æ‰¹æ¬¡å·
     * @return å·¥åºæµè½¬åˆ—表
     */
    List<MesWmBatchProcessTraceRespVO> selectProcessTraceList(String batchCode);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -773,6 +773,9 @@
    ErrorCode PD_PROJECT_NOT_UPLOAD_FOR_AUDIT = new ErrorCode(1_040_800_005, "只有待审核状态的设计项目才能审核");
    ErrorCode PD_PROJECT_NOT_AUDIT_FOR_ARCHIVE = new ErrorCode(1_040_800_006, "只有待归档状态的设计项目才能归档");
    ErrorCode PD_PROJECT_AUDIT_NOT_FINISHED = new ErrorCode(1_040_800_007, "该项目下存在未审核的资料,请先完成所有资料审核");
    ErrorCode PD_PROJECT_NOT_UPLOAD_FOR_APPROVAL = new ErrorCode(1_040_800_008, "只有待审核状态的设计项目才能提交审批");
    ErrorCode PD_PROJECT_NOT_APPROVING = new ErrorCode(1_040_800_009, "设计项目不处于审批中状态");
    ErrorCode PD_PROJECT_BPM_CATEGORY_NOT_EXISTS = new ErrorCode(1_040_800_010, "设计项目审批分类不存在,请先配置 BPM å®¡æ‰¹æµç¨‹");
    // ========== MES äº§å“è®¾è®¡-资料子表(1-040-801-000) ==========
    ErrorCode PD_DOCUMENT_NOT_EXISTS = new ErrorCode(1_040_801_000, "设计资料不存在");
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java
@@ -32,5 +32,9 @@
     * å·²å½’æ¡£
     */
    public static final int ARCHIVE = 4;
    /**
     * å®¡æ‰¹ä¸­ï¼ˆBPM å®¡æ‰¹è¿›è¡Œä¸­ï¼‰
     */
    public static final int APPROVING = 5;
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java
@@ -39,7 +39,7 @@
    /**
     * å¾…å½’æ¡£
     *
     * å¯¹åº” MesPdProjectService#auditProject æ–¹æ³•
     * å¯¹åº” MesPdProjectService#updateProjectAuditStatus æ–¹æ³•(BPM å®¡æ‰¹é€šè¿‡åŽï¼‰
     */
    AUDIT(MesPdStatusConstants.AUDIT, "待归档"),
    /**
@@ -47,7 +47,13 @@
     *
     * å¯¹åº” MesPdProjectService#archiveProject æ–¹æ³•
     */
    ARCHIVE(MesPdStatusConstants.ARCHIVE, "已归档");
    ARCHIVE(MesPdStatusConstants.ARCHIVE, "已归档"),
    /**
     * å®¡æ‰¹ä¸­
     *
     * å¯¹åº” MesPdProjectService#submitProjectApproval æ–¹æ³•
     */
    APPROVING(MesPdStatusConstants.APPROVING, "审批中");
    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesPdProjectStatusEnum::getStatus).toArray(Integer[]::new);
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java
@@ -81,11 +81,29 @@
    void uploadProject(Long id, Long userId);
    /**
     * å®¡æ ¸è®¾è®¡é¡¹ç›®ï¼ˆå¾…审核→待归档)
     * æäº¤è®¾è®¡é¡¹ç›®å®¡æ‰¹ï¼ˆå¾…审核→审批中)
     * <p>
     * åˆ›å»º BPM æµç¨‹å®žä¾‹ï¼Œå®¡æ‰¹é€šè¿‡åŽç”±ç›‘听器回调更新为待归档
     *
     * @param id ç¼–号
     * @param processDefinitionKey æµç¨‹å®šä¹‰ Key
     */
    void auditProject(Long id);
    void submitProjectApproval(Long id, String processDefinitionKey);
    /**
     * èŽ·å¾—è®¾è®¡é¡¹ç›®å®¡æ‰¹æµç¨‹åˆ—è¡¨
     *
     * @return æµç¨‹åˆ—表(id/name/version/key)
     */
    List<Map<String, Object>> getProjectApproveProcessDefinitionList();
    /**
     * æ›´æ–°è®¾è®¡é¡¹ç›®å®¡æ‰¹çŠ¶æ€ï¼ˆBPM ç›‘听器回调)
     *
     * @param id       ç¼–号
     * @param bpmResult BPM å®¡æ‰¹ç»“æžœ
     */
    void updateProjectAuditStatus(Long id, Integer bpmResult);
    /**
     * å½’档设计项目(待归档→已归档)
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
@@ -1,6 +1,14 @@
package cn.iocoder.yudao.module.mes.service.pd.project;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmCategoryDO;
import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
import cn.iocoder.yudao.module.bpm.enums.task.BpmTaskStatusEnum;
import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.pd.project.vo.MesPdProjectSaveReqVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
@@ -16,18 +24,24 @@
import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
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;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
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.convertMap;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
/**
@@ -37,7 +51,13 @@
 */
@Service
@Validated
@Slf4j
public class MesPdProjectServiceImpl implements MesPdProjectService {
    /**
     * BPM äº§å“é¡¹ç›®å®¡æ‰¹åˆ†ç±»ç¼–码
     */
    private static final String BPM_PROCESS_DEFINITION_CATEGORY_CODE = "mes_pd_project_approve";
    @Resource
    private MesPdProjectMapper projectMapper;
@@ -55,6 +75,15 @@
    @Resource
    @Lazy
    private MesMdAutoCodeRecordService autoCodeRecordService;
    @Resource
    private BpmProcessInstanceApi processInstanceApi;
    @Resource
    private BpmCategoryService bpmCategoryService;
    @Resource
    private BpmProcessDefinitionService bpmProcessDefinitionService;
    @Override
    @Transactional(rollbackFor = Exception.class)
@@ -180,27 +209,105 @@
    }
    @Override
    public void auditProject(Long id) {
        // æ ¡éªŒå­˜åœ¨
    @Transactional(rollbackFor = Exception.class)
    public void submitProjectApproval(Long id, String processDefinitionKey) {
        // 1. æ ¡éªŒå­˜åœ¨ + å¾…审核状态
        MesPdProjectDO project = validateProjectExists(id);
        // æ ¡éªŒçŠ¶æ€ä¸ºå¾…å®¡æ ¸
        if (!MesPdProjectStatusEnum.UPLOAD.getStatus().equals(project.getStatus())) {
            throw exception(PD_PROJECT_NOT_UPLOAD_FOR_AUDIT);
            throw exception(PD_PROJECT_NOT_UPLOAD_FOR_APPROVAL);
        }
        // æ ¡éªŒè¯¥é¡¹ç›®ä¸‹æ‰€æœ‰èµ„料审核记录均已审核(不存在待审核的记录)
        // 2. æ ¡éªŒè¯¥é¡¹ç›®ä¸‹æ‰€æœ‰èµ„料审核记录均已审核(不存在待审核的记录)
        List<MesPdDocumentDO> documents = documentService.getDocumentListByProjectId(id);
        List<Long> documentIds = documents.stream().map(MesPdDocumentDO::getId).toList();
        if (documentAuditMapper.existsPendingAuditByDocumentIds(documentIds)) {
            throw exception(PD_PROJECT_AUDIT_NOT_FINISHED);
        }
        // æ›´æ–°çŠ¶æ€
        // 3. åˆ›å»º BPM æµç¨‹å®žä¾‹
        String processInstanceId = processInstanceApi.createProcessInstance(getLoginUserId(),
                new BpmProcessInstanceCreateReqDTO()
                        .setProcessDefinitionKey(processDefinitionKey)
                        .setBusinessKey(String.valueOf(id)));
        // 4. æ›´æ–°é¡¹ç›®çŠ¶æ€ä¸ºå®¡æ‰¹ä¸­
        projectMapper.updateById(MesPdProjectDO.builder()
                .id(id)
                .status(MesPdProjectStatusEnum.AUDIT.getStatus())
                .status(MesPdProjectStatusEnum.APPROVING.getStatus())
                .processInstanceId(processInstanceId)
                .processDefinitionKey(processDefinitionKey)
                .build());
    }
    @Override
    public List<Map<String, Object>> getProjectApproveProcessDefinitionList() {
        // 1. æ ¡éªŒåˆ†ç±»æ˜¯å¦å­˜åœ¨
        List<BpmCategoryDO> categories = bpmCategoryService.getCategoryListByCode(
                Collections.singletonList(BPM_PROCESS_DEFINITION_CATEGORY_CODE));
        if (CollUtil.isEmpty(categories)) {
            throw exception(PD_PROJECT_BPM_CATEGORY_NOT_EXISTS);
        }
        // 2. èŽ·å–åˆ†ç±»ä¸‹çš„æµç¨‹å®šä¹‰ä¿¡æ¯
        List<BpmProcessDefinitionInfoDO> definitionInfoList = bpmProcessDefinitionService
                .getProcessDefinitionInfoListByCategory(BPM_PROCESS_DEFINITION_CATEGORY_CODE);
        if (CollUtil.isEmpty(definitionInfoList)) {
            return Collections.emptyList();
        }
        // 3. è¿‡æ»¤æ¿€æ´»çŠ¶æ€ï¼Œä¿ç•™æœ€æ–°ç‰ˆæœ¬
        Map<String, ProcessDefinition> latestVersionMap = new HashMap<>();
        for (BpmProcessDefinitionInfoDO info : definitionInfoList) {
            ProcessDefinition pd = bpmProcessDefinitionService.getProcessDefinition(info.getProcessDefinitionId());
            if (pd == null || pd.isSuspended()) {
                continue;
            }
            ProcessDefinition existing = latestVersionMap.get(pd.getKey());
            if (existing == null || pd.getVersion() > existing.getVersion()) {
                latestVersionMap.put(pd.getKey(), pd);
            }
        }
        // 4. è¿”回流程定义列表
        List<Map<String, Object>> result = new ArrayList<>();
        for (ProcessDefinition pd : latestVersionMap.values()) {
            Map<String, Object> item = new HashMap<>();
            item.put("id", pd.getId());
            item.put("key", pd.getKey());
            item.put("name", pd.getName());
            result.add(item);
        }
        return result;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void updateProjectAuditStatus(Long id, Integer bpmResult) {
        // 1. æ ¡éªŒå­˜åœ¨
        MesPdProjectDO project = validateProjectExists(id);
        if (!MesPdProjectStatusEnum.APPROVING.getStatus().equals(project.getStatus())) {
            log.warn("[updateProjectAuditStatus] è®¾è®¡é¡¹ç›®({}) ä¸å¤„于审批中状态,当前状态:{}", id, project.getStatus());
            return;
        }
        // 2. æ ¹æ®å®¡æ‰¹ç»“果更新状态
        Integer newStatus = convertBpmResultToProjectStatus(bpmResult);
        if (newStatus != null) {
            projectMapper.updateById(MesPdProjectDO.builder()
                    .id(id)
                    .status(newStatus)
                    .build());
        }
    }
    /**
     * è½¬æ¢ BPM å®¡æ‰¹ç»“果为设计项目状态
     */
    private Integer convertBpmResultToProjectStatus(Integer bpmResult) {
        if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.AUDIT.getStatus();
        } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.UPLOAD.getStatus();
        } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
            return MesPdProjectStatusEnum.UPLOAD.getStatus();
        }
        return null;
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void archiveProject(Long id, Long userId) {
        // æ ¡éªŒå­˜åœ¨
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.mes.service.pd.project.listener;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
import cn.iocoder.yudao.module.mes.service.pd.project.MesPdProjectService;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
 * è®¾è®¡é¡¹ç›®å®¡æ‰¹çŠ¶æ€ç›‘å¬å™¨
 *
 * ç›‘听 BPM æµç¨‹å®žä¾‹çŠ¶æ€å˜æ›´äº‹ä»¶ï¼Œæ›´æ–°è®¾è®¡é¡¹ç›®çš„å®¡æ‰¹çŠ¶æ€
 *
 * @author è¶…级管理员
 */
@Component
@Slf4j
public class MesPdProjectStatusListener extends BpmProcessInstanceStatusEventListener {
    /**
     * BPM è®¾è®¡é¡¹ç›®å®¡æ‰¹æµç¨‹å®šä¹‰ key
     */
    private static final String BPM_PROCESS_DEFINITION_KEY = "mes-pd-project-audit";
    @Resource
    private MesPdProjectService projectService;
    @Override
    protected String getProcessDefinitionKey() {
        return BPM_PROCESS_DEFINITION_KEY;
    }
    @Override
    protected void onEvent(BpmProcessInstanceStatusEvent event) {
        Long projectId;
        try {
            projectId = Long.parseLong(event.getBusinessKey());
        } catch (NumberFormatException e) {
            log.warn("[onEvent] businessKey({}) ä¸æ˜¯æœ‰æ•ˆçš„设计项目编号", event.getBusinessKey());
            return;
        }
        log.info("[onEvent] æ”¶åˆ°BPM审批事件,projectId={}, status={}", projectId, event.getStatus());
        projectService.updateProjectAuditStatus(projectId, event.getStatus());
    }
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
@@ -3,6 +3,9 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import java.util.List;
@@ -96,4 +99,36 @@
     */
    Long getBatchCountByToolId(Long toolId);
    /**
     * èŽ·å¾—æ‰¹æ¬¡é‡‡è´­æ¥æºï¼ˆé‡‡è´­å…¥åº“å• + IQC)
     *
     * @param code æ‰¹æ¬¡å·
     * @return é‡‡è´­æ¥æºåˆ—表
     */
    List<MesWmBatchPurchaseSourceRespVO> getPurchaseSourceList(String code);
    /**
     * èŽ·å¾—æ‰¹æ¬¡é”€å”®åŽ»å‘ï¼ˆé”€å”®å‡ºåº“å• + OQC)
     *
     * @param code æ‰¹æ¬¡å·
     * @return é”€å”®åŽ»å‘åˆ—è¡¨
     */
    List<MesWmBatchSalesTargetRespVO> getSalesTargetList(String code);
    /**
     * èŽ·å¾—æ‰¹æ¬¡å·¥åºæµè½¬ï¼ˆäº§å‡º/投入事件)
     *
     * @param code æ‰¹æ¬¡å·
     * @return å·¥åºæµè½¬åˆ—表
     */
    List<MesWmBatchProcessTraceRespVO> getProcessTraceList(String code);
    /**
     * æ ¹æ® SN ç èŽ·å¾—æ‰¹æ¬¡
     *
     * @param sn SN ç 
     * @return æ‰¹æ¬¡è®°å½•(未命中返回 null)
     */
    MesWmBatchDO getBatchBySn(String sn);
}
yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -6,9 +6,14 @@
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchGenerateReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPageReqVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO;
import cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO;
import cn.iocoder.yudao.module.mes.dal.dataobject.mdm.MesMdmItemDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.batch.MesWmBatchDO;
import cn.iocoder.yudao.module.mes.dal.dataobject.wm.sn.MesWmSnDO;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.batch.MesWmBatchMapper;
import cn.iocoder.yudao.module.mes.dal.mysql.wm.sn.MesWmSnMapper;
import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
import cn.iocoder.yudao.module.mes.enums.md.autocode.MesMdAutoCodeRuleCodeEnum;
import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
@@ -24,6 +29,7 @@
import org.springframework.validation.annotation.Validated;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -51,6 +57,8 @@
    @Resource
    private MesMdmItemService mdmItemService;
    @Resource
    private MesWmSnMapper snMapper;
    @Resource
    private MdmItemBatchConfigApi itemBatchConfigApi;
    @Resource
@@ -372,4 +380,39 @@
        return batchMapper.selectCountByToolId(toolId);
    }
    @Override
    public List<MesWmBatchPurchaseSourceRespVO> getPurchaseSourceList(String code) {
        return batchMapper.selectPurchaseSourceList(code);
    }
    @Override
    public List<MesWmBatchSalesTargetRespVO> getSalesTargetList(String code) {
        return batchMapper.selectSalesTargetList(code);
    }
    @Override
    public List<MesWmBatchProcessTraceRespVO> getProcessTraceList(String code) {
        List<MesWmBatchProcessTraceRespVO> list = batchMapper.selectProcessTraceList(code);
        if (CollUtil.isEmpty(list)) {
            return list;
        }
        // æŒ‰å·¥å•、工序序号、事件时间排序(sort å¯èƒ½ä¸ºç©ºï¼Œç©ºå€¼å…œåº•)
        list.sort(Comparator.comparing(MesWmBatchProcessTraceRespVO::getWorkOrderId,
                        Comparator.nullsLast(Comparator.naturalOrder()))
                .thenComparing(MesWmBatchProcessTraceRespVO::getProcessSort,
                        Comparator.nullsLast(Comparator.naturalOrder()))
                .thenComparing(MesWmBatchProcessTraceRespVO::getEventTime,
                        Comparator.nullsLast(Comparator.naturalOrder())));
        return list;
    }
    @Override
    public MesWmBatchDO getBatchBySn(String sn) {
        MesWmSnDO snDO = snMapper.selectOne(MesWmSnDO::getCode, sn);
        if (snDO == null || StrUtil.isBlank(snDO.getBatchCode())) {
            return null;
        }
        return batchMapper.selectByCode(snDO.getBatchCode());
    }
}
yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
@@ -11,14 +11,15 @@
    <!--
        å‘前追溯:从某个原材料批次出发,查找它被哪些工单消耗后,产出了哪些成品批次
        SQL è·¯å¾„:库存事务(消耗 OUT) â†’ æ¶ˆè€—单 â†’ æŠ¥å·¥è®°å½• â†’ åº“存事务(产出 IN) â†’ æ‰¹æ¬¡
        é“¾è·¯ï¼šåº“存事务(消耗 OUT, biz=123, biz_id=消耗单id) â†’ æ¶ˆè€—单 â†’ æŠ¥å·¥ â†’ äº§å‡ºå•(feedback_id) â†’ åº“存事务(产出 IN, biz=124, biz_id=产出单id) â†’ æ‰¹æ¬¡
    -->
    <select id="selectListByForward" parameterType="String" resultMap="BatchTraceResult">
        SELECT DISTINCT pf.work_order_id, pf.item_id, tx_produce.batch_id, tx_produce.batch_code
        FROM mes_wm_transaction tx_consume
        LEFT JOIN mes_wm_item_consume ic ON tx_consume.biz_id = ic.id AND tx_consume.biz_type = 123 AND ic.deleted = 0
        LEFT JOIN mes_pro_feedback pf ON pf.id = ic.feedback_id AND pf.deleted = 0
        LEFT JOIN mes_wm_transaction tx_produce ON tx_produce.biz_id = pf.id AND tx_produce.biz_type = 124 AND tx_produce.deleted = 0
        JOIN mes_wm_item_consume ic ON tx_consume.biz_id = ic.id AND tx_consume.biz_type = 123 AND ic.deleted = 0
        JOIN mes_pro_feedback pf ON pf.id = ic.feedback_id AND pf.deleted = 0
        JOIN mes_wm_product_produce pp ON pp.feedback_id = pf.id AND pp.deleted = 0
        JOIN mes_wm_transaction tx_produce ON tx_produce.biz_id = pp.id AND tx_produce.biz_type = 124 AND tx_produce.deleted = 0
        WHERE tx_consume.batch_code = #{batchCode}
        AND tx_consume.biz_type = 123
        AND tx_consume.deleted = 0
@@ -27,19 +28,65 @@
    <!--
        å‘后追溯:从某个成品批次出发,查找生产它时消耗了哪些原材料批次
        SQL è·¯å¾„:库存事务(产出 IN) â†’ äº§å‡ºå• â†’ æŠ¥å·¥è®°å½• â†’ åº“存事务(消耗 OUT) â†’ æ‰¹æ¬¡
        é“¾è·¯ï¼šåº“存事务(产出 IN, biz=124, biz_id=产出单id) â†’ äº§å‡ºå• â†’ æŠ¥å·¥ â†’ æ¶ˆè€—单(feedback_id) â†’ åº“存事务(消耗 OUT, biz=123, biz_id=消耗单id) â†’ æ‰¹æ¬¡
    -->
    <select id="selectListByBackward" parameterType="String" resultMap="BatchTraceResult">
        SELECT DISTINCT ic.work_order_id, tx_consume.item_id, tx_consume.batch_id, tx_consume.batch_code
        FROM mes_wm_transaction tx_produce
        LEFT JOIN mes_wm_product_produce pp ON tx_produce.biz_id = pp.id AND tx_produce.biz_type = 124 AND pp.deleted = 0
        LEFT JOIN mes_pro_feedback pf ON pf.id = pp.feedback_id AND pf.deleted = 0
        LEFT JOIN mes_wm_transaction tx_consume ON tx_consume.biz_id = pf.id AND tx_consume.biz_type = 123 AND tx_consume.deleted = 0
        LEFT JOIN mes_wm_item_consume ic ON ic.id = tx_consume.biz_id AND ic.deleted = 0
        JOIN mes_wm_product_produce pp ON tx_produce.biz_id = pp.id AND tx_produce.biz_type = 124 AND pp.deleted = 0
        JOIN mes_pro_feedback pf ON pf.id = pp.feedback_id AND pf.deleted = 0
        JOIN mes_wm_item_consume ic ON ic.feedback_id = pf.id AND ic.deleted = 0
        JOIN mes_wm_transaction tx_consume ON tx_consume.biz_id = ic.id AND tx_consume.biz_type = 123 AND tx_consume.deleted = 0
        WHERE tx_produce.batch_code = #{batchCode}
        AND tx_produce.biz_type = 124
        AND tx_produce.deleted = 0
        AND tx_consume.batch_id IS NOT NULL
    </select>
    <!-- æ‰¹æ¬¡é‡‡è´­æ¥æºï¼šæ‰¹æ¬¡ â†’ é‡‡è´­å…¥åº“行 â†’ é‡‡è´­å…¥åº“单 â†’ IQC -->
    <select id="selectPurchaseSourceList" parameterType="String"
            resultType="cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchPurchaseSourceRespVO">
        SELECT r.code AS receipt_code, r.receipt_date, r.purchase_order_code, r.vendor_id,
               iqc.code AS iqc_code, iqc.check_result AS check_result,
               rl.batch_code, rl.item_id, rl.received_quantity
        FROM mes_wm_item_receipt_line rl
        JOIN mes_wm_item_receipt r ON r.id = rl.receipt_id AND r.deleted = 0
        LEFT JOIN mes_qc_iqc iqc ON iqc.id = r.iqc_id AND iqc.deleted = 0
        WHERE rl.batch_code = #{batchCode} AND rl.deleted = 0
    </select>
    <!-- æ‰¹æ¬¡é”€å”®åŽ»å‘ï¼šæ‰¹æ¬¡ â†’ é”€å”®å‡ºåº“行 â†’ é”€å”®å‡ºåº“单 â†’ OQC -->
    <select id="selectSalesTargetList" parameterType="String"
            resultType="cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchSalesTargetRespVO">
        SELECT s.code AS sales_code, s.sales_date, s.sales_order_code, s.client_id,
               oqc.code AS oqc_code, oqc.check_result AS check_result,
               sl.batch_code, sl.item_id, sl.quantity, sl.quality_status
        FROM mes_wm_product_sales_line sl
        JOIN mes_wm_product_sales s ON s.id = sl.sales_id AND s.deleted = 0
        LEFT JOIN mes_qc_oqc oqc ON oqc.id = sl.oqc_id AND oqc.deleted = 0
        WHERE sl.batch_code = #{batchCode} AND sl.deleted = 0
    </select>
    <!-- æ‰¹æ¬¡å·¥åºæµè½¬ï¼šäº§å‡º(OUT)/投入(IN) äº‹ä»¶èšåˆï¼Œæ— éœ€å»ºè¡¨ -->
    <select id="selectProcessTraceList" parameterType="String"
            resultType="cn.iocoder.yudao.module.mes.controller.admin.wm.batch.vo.MesWmBatchProcessTraceRespVO">
        SELECT 'OUT' AS event_type, pp.work_order_id, pp.process_id,
               ppl.batch_code, ppl.quantity, ppl.quality_status, pp.produce_date AS event_time,
               wop.sort AS process_sort
        FROM mes_wm_product_produce_line ppl
        JOIN mes_wm_product_produce pp ON pp.id = ppl.produce_id AND pp.deleted = 0
        LEFT JOIN mes_pro_work_order_process wop
               ON wop.work_order_id = pp.work_order_id AND wop.process_id = pp.process_id AND wop.deleted = 0
        WHERE ppl.batch_code = #{batchCode} AND ppl.deleted = 0
        UNION ALL
        SELECT 'IN' AS event_type, ic.work_order_id, ic.process_id,
               icd.batch_code, icd.quantity, NULL AS quality_status, ic.consume_date AS event_time,
               wop.sort AS process_sort
        FROM mes_wm_item_consume_detail icd
        JOIN mes_wm_item_consume ic ON ic.id = icd.consume_id AND ic.deleted = 0
        LEFT JOIN mes_pro_work_order_process wop
               ON wop.work_order_id = ic.work_order_id AND wop.process_id = ic.process_id AND wop.deleted = 0
        WHERE icd.batch_code = #{batchCode} AND icd.deleted = 0
    </select>
</mapper>
yudao-server/src/main/resources/application-yzfx.yaml
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,324 @@
server:
  port: 48080
--- #################### æ•°æ®åº“相关配置 ####################
spring:
  autoconfigure:
    # noinspection SpringBootApplicationYaml
    exclude:
      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # é»˜è®¤ local çŽ¯å¢ƒï¼Œä¸å¼€å¯ Quartz çš„自动配置
      - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # ç¦ç”¨ AI æ¨¡å—çš„ Qdrant,手动创建
      - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # ç¦ç”¨ AI æ¨¡å—çš„ Redis,手动创建
      - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # ç¦ç”¨ AI æ¨¡å—çš„ Milvus,手动创建
      - org.dromara.trans.config.TransServiceConfig # TODO èŠ‹è‰¿ï¼šeasy-trans å…¼å®¹ Spring Boot 4 åŽç§»é™¤ï¼ˆTransServiceConfig å¼•用了已移除的 RestTemplateBuilder)
      # ç¦ç”¨æœªé…ç½® API Key çš„æ¨¡åž‹è‡ªåŠ¨é…ç½®ï¼ˆåªä¿ç•™é€šä¹‰åƒé—® Embedding çš„æ‰‹åŠ¨é…ç½®ï¼‰
      - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
      - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration
      - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration
      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration
      - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration
      - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration
      - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration
      - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration
      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration
      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration
      - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration
      - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
      - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration
      - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration
  # æ•°æ®æºé…ç½®é¡¹
  datasource:
    druid: # Druid ã€ç›‘控】相关的全局配置
      web-stat-filter:
        enabled: true
      stat-view-servlet:
        enabled: true
        allow: # è®¾ç½®ç™½åå•,不填则允许所有访问
        url-pattern: /druid/*
        login-username: # æŽ§åˆ¶å°ç®¡ç†ç”¨æˆ·åå’Œå¯†ç 
        login-password:
      filter:
        stat:
          enabled: true
          log-slow-sql: true # æ…¢ SQL è®°å½•
          slow-sql-millis: 100
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
    dynamic: # å¤šæ•°æ®æºé…ç½®
      druid: # Druid ã€è¿žæŽ¥æ± ã€‘相关的全局配置
        initial-size: 1 # åˆå§‹è¿žæŽ¥æ•°
        min-idle: 1 # æœ€å°è¿žæŽ¥æ± æ•°é‡
        max-active: 20 # æœ€å¤§è¿žæŽ¥æ± æ•°é‡
        max-wait: 60000 # é…ç½®èŽ·å–è¿žæŽ¥ç­‰å¾…è¶…æ—¶çš„æ—¶é—´ï¼Œå•ä½ï¼šæ¯«ç§’ï¼ˆ1 åˆ†é’Ÿï¼‰
        time-between-eviction-runs-millis: 60000 # é…ç½®é—´éš”多久才进行一次检测,检测需要关闭的空闲连接,单位:毫秒(1 åˆ†é’Ÿï¼‰
        min-evictable-idle-time-millis: 600000 # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å°ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位:毫秒(10 åˆ†é’Ÿï¼‰
        max-evictable-idle-time-millis: 1800000 # é…ç½®ä¸€ä¸ªè¿žæŽ¥åœ¨æ± ä¸­æœ€å¤§ç”Ÿå­˜çš„æ—¶é—´ï¼Œå•位:毫秒(30 åˆ†é’Ÿï¼‰
        validation-query: SELECT 1 FROM DUAL # é…ç½®æ£€æµ‹è¿žæŽ¥æ˜¯å¦æœ‰æ•ˆ
        test-while-idle: true
        test-on-borrow: false
        test-on-return: false
        pool-prepared-statements: true # æ˜¯å¦å¼€å¯ PreparedStatement ç¼“å­˜
        max-pool-prepared-statement-per-connection-size: 20 # æ¯ä¸ªè¿žæŽ¥ç¼“存的 PreparedStatement æ•°é‡
      primary: master
      datasource:
        master:
          url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X è¿žæŽ¥çš„示例
          #          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL è¿žæŽ¥çš„示例
          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle è¿žæŽ¥çš„示例
          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer è¿žæŽ¥çš„示例
          #          url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM è¿žæŽ¥çš„示例
          #          url: jdbc:kingbase8://127.0.0.1:54321/test  # äººå¤§é‡‘仓 KingbaseES è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss è¿žæŽ¥çš„示例
          #          url: jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
          username: root
          password: yzfx@123456..
          #          username: sa # SQL Server è¿žæŽ¥çš„示例
          #          password: Yudao@2024 # SQL Server è¿žæŽ¥çš„示例
          #          username: SYSDBA # DM è¿žæŽ¥çš„示例
          #          password: SYSDBA001 # DM è¿žæŽ¥çš„示例
          #          username: root # OpenGauss è¿žæŽ¥çš„示例
          #          password: Yudao@2024 # OpenGauss è¿žæŽ¥çš„示例
          #          username: root # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
          #          password: 123456 # ç€šé«˜ HighGo è¿žæŽ¥çš„示例
        slave: # æ¨¡æ‹Ÿä»Žåº“,可根据自己需要修改
          lazy: true # å¼€å¯æ‡’加载,保证启动速度
          url: jdbc:mysql://172.17.0.1:9002/ruoyi-vue-pro?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&rewriteBatchedStatements=true&nullCatalogMeansCurrent=true
          username: root
          password: yzfx@123456
#        tdengine: # IoT æ•°æ®åº“(需要 IoT ç‰©è”网再开启噢!)
#          lazy: true # å¼€å¯æ‡’加载,保证启动速度
#          url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # å‚见:https://t.zsxq.com/qaX9c,开启 TDengine WebSocket è‡ªåŠ¨é‡è¿ž
#          driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
#          username: root
#          password: taosdata
#          druid:
#            validation-query: SELECT SERVER_STATUS() # TDengine æ•°æ®æºçš„æœ‰æ•ˆæ€§æ£€æŸ¥ SQL
  # Redis é…ç½®ã€‚Redisson é»˜è®¤çš„配置足够使用,一般不需要进行调优
  data:
    redis:
      host: 172.17.0.1 # åœ°å€
      port: 6379 # ç«¯å£
      database: 1 # æ•°æ®åº“索引
#      password: dev # å¯†ç ï¼Œå»ºè®®ç”Ÿäº§çŽ¯å¢ƒå¼€å¯
--- #################### å®šæ—¶ä»»åŠ¡ç›¸å…³é…ç½® ####################
# Quartz é…ç½®é¡¹ï¼Œå¯¹åº” QuartzProperties é…ç½®ç±»
spring:
  quartz:
    auto-startup: true # æœ¬åœ°å¼€å‘环境,尽量不要开启 Job
    scheduler-name: schedulerName # Scheduler åå­—。默认为 schedulerName
    job-store-type: jdbc # Job å­˜å‚¨å™¨ç±»åž‹ã€‚默认为 memory è¡¨ç¤ºå†…存,可选 jdbc ä½¿ç”¨æ•°æ®åº“。
    wait-for-jobs-to-complete-on-shutdown: true # åº”用关闭时,是否等待定时任务执行完成。默认为 false ï¼Œå»ºè®®è®¾ç½®ä¸º true
    properties: # æ·»åŠ  Quartz Scheduler é™„加属性,更多可以看 http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html æ–‡æ¡£
      org:
        quartz:
          # Scheduler ç›¸å…³é…ç½®
          scheduler:
            instanceName: schedulerName
            instanceId: AUTO # è‡ªåŠ¨ç”Ÿæˆ instance ID
          # JobStore ç›¸å…³é…ç½®
          jobStore:
            # JobStore å®žçŽ°ç±»ã€‚å¯è§åšå®¢ï¼šhttps://blog.csdn.net/weixin_42458219/article/details/122247162
            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
            isClustered: true # æ˜¯é›†ç¾¤æ¨¡å¼
            clusterCheckinInterval: 15000 # é›†ç¾¤æ£€æŸ¥é¢‘率,单位:毫秒。默认为 15000,即 15 ç§’
            misfireThreshold: 60000 # misfire é˜€å€¼ï¼Œå•位:毫秒。
          # çº¿ç¨‹æ± ç›¸å…³é…ç½®
          threadPool:
            threadCount: 25 # çº¿ç¨‹æ± å¤§å°ã€‚默认为 10 ã€‚
            threadPriority: 5 # çº¿ç¨‹ä¼˜å…ˆçº§
            class: org.quartz.simpl.SimpleThreadPool # çº¿ç¨‹æ± ç±»åž‹
    jdbc: # ä½¿ç”¨ JDBC çš„ JobStore çš„æ—¶å€™ï¼ŒJDBC çš„配置
      initialize-schema: NEVER # æ˜¯å¦è‡ªåŠ¨ä½¿ç”¨ SQL åˆå§‹åŒ– Quartz è¡¨ç»“构。这里设置成 never ï¼Œæˆ‘们手动创建表结构。
--- #################### æ¶ˆæ¯é˜Ÿåˆ—相关 ####################
# rocketmq é…ç½®é¡¹ï¼Œå¯¹åº” RocketMQProperties é…ç½®ç±»
rocketmq:
  name-server: 127.0.0.1:9876 # RocketMQ Namesrv
spring:
  # RabbitMQ é…ç½®é¡¹ï¼Œå¯¹åº” RabbitProperties é…ç½®ç±»
  rabbitmq:
    host: 127.0.0.1 # RabbitMQ æœåŠ¡çš„åœ°å€
    port: 5672 # RabbitMQ æœåŠ¡çš„ç«¯å£
    username: rabbit # RabbitMQ æœåŠ¡çš„è´¦å·
    password: rabbit # RabbitMQ æœåŠ¡çš„å¯†ç 
  # Kafka é…ç½®é¡¹ï¼Œå¯¹åº” KafkaProperties é…ç½®ç±»
  kafka:
    bootstrap-servers: 127.0.0.1:9092 # æŒ‡å®š Kafka Broker åœ°å€ï¼Œå¯ä»¥è®¾ç½®å¤šä¸ªï¼Œä»¥é€—号分隔
--- #################### æœåŠ¡ä¿éšœç›¸å…³é…ç½® ####################
# Lock4j é…ç½®é¡¹
lock4j:
  acquire-timeout: 3000 # èŽ·å–åˆ†å¸ƒå¼é”è¶…æ—¶æ—¶é—´ï¼Œé»˜è®¤ä¸º 3000 æ¯«ç§’
  expire: 30000 # åˆ†å¸ƒå¼é”çš„超时时间,默认为 30000 æ¯«ç§’
--- #################### ç›‘控相关配置 ####################
# Actuator ç›‘控端点的配置项
management:
  endpoints:
    web:
      base-path: /actuator # Actuator æä¾›çš„ API æŽ¥å£çš„æ ¹ç›®å½•。默认为 /actuator
      exposure:
        include: '*' # éœ€è¦å¼€æ”¾çš„端点。默认值只打开 health å’Œ info ä¸¤ä¸ªç«¯ç‚¹ã€‚通过设置 * ï¼Œå¯ä»¥å¼€æ”¾æ‰€æœ‰ç«¯ç‚¹ã€‚
# Spring Boot Admin é…ç½®é¡¹
spring:
  boot:
    admin:
      # Spring Boot Admin Client å®¢æˆ·ç«¯çš„相关配置
      client:
        url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # è®¾ç½® Spring Boot Admin Server åœ°å€
        instance:
          service-host-type: IP # æ³¨å†Œå®žä¾‹æ—¶ï¼Œä¼˜å…ˆä½¿ç”¨ IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
        username: admin
        password: admin
      # Spring Boot Admin Server æœåŠ¡ç«¯çš„ç›¸å…³é…ç½®
      context-path: /admin # é…ç½® Spring
      # å…è®¸åµŒå…¥ iframe çš„域名(支持通配符),实际部署时,可以改为 "'self' [你的公网域名]"
      frame-ancestors: "'self' localhost localhost:48080 127.0.0.1 127.0.0.1:48080"
# æ—¥å¿—文件配置
logging:
  file:
    name: logs/${spring.application.name}.log # æ—¥å¿—文件名,相对于项目根目录
  level:
    # é…ç½®è‡ªå·±å†™çš„ MyBatis Mapper æ‰“印日志
    cn.iocoder.yudao.module.bpm.dal.mysql: debug
    cn.iocoder.yudao.module.infra.dal.mysql: debug
    cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # é…ç½® ApiErrorLogMapper çš„æ—¥å¿—级别为 info,避免和 GlobalExceptionHandler é‡å¤æ‰“印
    cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # é…ç½® JobLogMapper çš„æ—¥å¿—级别为 info
    cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # é…ç½® FileConfigMapper çš„æ—¥å¿—级别为 info
    cn.iocoder.yudao.module.pay.dal.mysql: debug
    cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # é…ç½® PayNotifyTaskMapper çš„æ—¥å¿—级别为 info
    cn.iocoder.yudao.module.system.dal.mysql: debug
    cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # é…ç½® SmsChannelMapper çš„æ—¥å¿—级别为 info
    cn.iocoder.yudao.module.tool.dal.mysql: debug
    cn.iocoder.yudao.module.member.dal.mysql: debug
    cn.iocoder.yudao.module.trade.dal.mysql: debug
    cn.iocoder.yudao.module.promotion.dal.mysql: debug
    cn.iocoder.yudao.module.statistics.dal.mysql: debug
    cn.iocoder.yudao.module.crm.dal.mysql: debug
    cn.iocoder.yudao.module.erp.dal.mysql: debug
    cn.iocoder.yudao.module.mes.dal.mysql: debug
    cn.iocoder.yudao.module.wms.dal.mysql: debug
    cn.iocoder.yudao.module.iot.dal.mysql: debug
    cn.iocoder.yudao.module.iot.dal.tdengine: DEBUG
    cn.iocoder.yudao.module.iot.service.rule: debug
    cn.iocoder.yudao.module.ai.dal.mysql: debug
    cn.iocoder.yudao.module.im.dal.mysql: debug
    org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO èŠ‹è‰¿ï¼šå…ˆç¦ç”¨ï¼ŒSpring Boot 3.X å­˜åœ¨éƒ¨åˆ†é”™è¯¯çš„ WARN æç¤º
debug: false
yudao:
  ai:
    vector-store:
      type: milvus # å‘量库类型:milvus
      milvus:
        initialize-schema: true
        database-name: test
        collection-name: knowledge_segment_yzfx
        host: 36.213.180.217
        port: 19530
--- #################### å¾®ä¿¡å…¬ä¼—号、小程序相关配置 ####################
wx:
  mp: # å…¬ä¼—号配置(必填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-mp-spring-boot-starter/README.md æ–‡æ¡£
#    app-id: wx041349c6f39b268b # æµ‹è¯•号(牛希尧提供的)
#    secret: 5abee519483bc9f8cb37ce280e814bd0
#    app-id: wx5b23ba7a5589ecbb # æµ‹è¯•号(自己的)
#    secret: 2a7b3b20c537e52e74afd395eb85f61f
    app-id: wxf56b1542b9e85f8a # æµ‹è¯•号(Kongdy æä¾›çš„)
    secret: 496379dcef1ba869e9234de8d598cfd3
    # å­˜å‚¨é…ç½®ï¼Œè§£å†³ AccessToken çš„跨节点的共享
    config-storage:
      type: RedisTemplate # é‡‡ç”¨ RedisTemplate æ“ä½œ Redis,会自动从 Spring ä¸­èŽ·å–
      key-prefix: wx # Redis Key çš„前缀
      http-client-type: HttpComponents # é‡‡ç”¨ HttpComponents(HttpClient 5)请求微信公众号平台
  miniapp: # å°ç¨‹åºé…ç½®ï¼ˆå¿…填),参见 https://github.com/Wechat-Group/WxJava/blob/develop/spring-boot-starters/wx-java-miniapp-spring-boot-starter/README.md æ–‡æ¡£
    #    appid: wx62056c0d5e8db250 # æµ‹è¯•号(牛希尧提供的)
    #    secret: 333ae72f41552af1e998fe1f54e1584a
#    appid: wx63c280fe3248a3e7 # wenhualian的接口测试号
#    secret: 6f270509224a7ae1296bbf1c8cb97aed
    appid: wxc4598c446f8a9cb3 # æµ‹è¯•号(Kongdy æä¾›çš„)
    secret: 4a1a04e07f6a4a0751b39c3064a92c8b
#    appid: wx66186af0759f47c9 # æµ‹è¯•号(puhui æä¾›çš„)
#    secret: 3218bcbd112cbc614c7264ceb20144ac
    config-storage:
      type: RedisTemplate # é‡‡ç”¨ RedisTemplate æ“ä½œ Redis,会自动从 Spring ä¸­èŽ·å–
      key-prefix: wa # Redis Key çš„前缀
      http-client-type: HttpComponents # é‡‡ç”¨ HttpComponents(HttpClient 5)请求微信小程序平台
--- #################### è¶…级管理员相关配置 ####################
# è¶…级管理员配置项,设置当前项目所有自定义的配置
yudao:
  captcha:
    enable: false # æœ¬åœ°çŽ¯å¢ƒï¼Œæš‚æ—¶å…³é—­å›¾ç‰‡éªŒè¯ç ï¼Œæ–¹ä¾¿ç™»å½•ç­‰æŽ¥å£çš„æµ‹è¯•ï¼›
  security:
    mock-enable: true
  pay:
    order-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/order # æ”¯ä»˜æ¸ é“的【支付】回调地址
    refund-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/refund # æ”¯ä»˜æ¸ é“的【退款】回调地址
    transfer-notify-url: https://yutou.mynatapp.cc/admin-api/pay/notify/transfer # æ”¯ä»˜æ¸ é“的【转账】回调地址
  access-log: # è®¿é—®æ—¥å¿—的配置项
    enable: false
  demo: false # å…³é—­æ¼”示模式
  wxa-code:
    env-version: develop # å°ç¨‹åºç‰ˆæœ¬: æ­£å¼ç‰ˆä¸º "release";体验版为 "trial";开发版为 "develop"
  wxa-subscribe-message:
    miniprogram-state: developer # è·³è½¬å°ç¨‹åºç±»åž‹ï¼šå¼€å‘版为 â€œdeveloper”;体验版为 â€œtrial”为;正式版为 â€œformal”
  tencent-lbs-key: TVDBZ-TDILD-4ON4B-PFDZA-RNLKH-VVF6E # QQ åœ°å›¾çš„密钥 https://lbs.qq.com/service/staticV2/staticGuide/staticDoc
  storage: # æ–‡ä»¶ä¸Šä¼ é…ç½®
    path: /data/javaWork/mom/file # æ–‡ä»¶å­˜å‚¨æ ¹ç›®å½•
    url-prefix: /admin-api/system/storage-blob # URL路径前缀
    domain: http://42.63.70.231:9001 # åŸŸåå‰ç¼€ï¼ˆæŒ‰å®žé™…域名配置),前端配置好像不太对现在是直连后端,所有要后端端口
    expired: 120 # token过期时间(分钟)
    use-limit: 10 # token使用次数上限
    jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT密钥
    compress: true # æ˜¯å¦åŽ‹ç¼©å›¾ç‰‡
    need-compress-size: 10MB # è§¦å‘压缩的文件大小阈值
    compress-quality: 0.5 # åŽ‹ç¼©è´¨é‡(0.0-1.0)
justauth:
  enabled: true
  type:
    DINGTALK: # é’‰é’‰
      client-id: dingvrnreaje3yqvzhxg
      client-secret: i8E6iZyDvZj51JIb0tYsYfVQYOks9Cq1lgryEjFRqC79P3iJcrxEwT6Qk2QvLrLI
      ignore-check-redirect-uri: true
    WECHAT_ENTERPRISE: # ä¼ä¸šå¾®ä¿¡
      client-id: wwd411c69a39ad2e54
      client-secret: 1wTb7hYxnpT2TUbIeHGXGo7T0odav1ic10mLdyyATOw
      agent-id: 1000004
      ignore-check-redirect-uri: true
    # noinspection SpringBootApplicationYaml
    WECHAT_MINI_PROGRAM: # å¾®ä¿¡å°ç¨‹åº
      client-id: ${wx.miniapp.appid}
      client-secret: ${wx.miniapp.secret}
      ignore-check-redirect-uri: true
      ignore-check-state: true # å¾®ä¿¡å°ç¨‹åºï¼Œä¸ä¼šä½¿ç”¨åˆ° state,所以不进行校验
    WECHAT_MP: # å¾®ä¿¡å…¬ä¼—号
      client-id: ${wx.mp.app-id}
      client-secret: ${wx.mp.secret}
      ignore-check-redirect-uri: true
    ALIPAY: # æ”¯ä»˜å®å°ç¨‹åº
      client-id: xx
      client-secret: xx
      alipay-public-key: xx
      ignore-check-redirect-uri: true
      ignore-check-state: true
  cache:
    type: REDIS
    prefix: 'social_auth_state:' # ç¼“存前缀,目前只对 Redis ç¼“存生效,默认 JUSTAUTH::STATE::
    timeout: 24h # è¶…时时长,目前只对 Redis ç¼“存生效,默认 3 åˆ†é’Ÿ