From bb0b80bd7ca7c1f979ab0125ae01f3faa1d14f4b Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期一, 17 八月 2026 09:37:57 +0800
Subject: [PATCH] Merge branch 'refs/heads/dev_business' into fix/hsy/20260811

---
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java                |    3 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java           |   27 
 docs/srm_supplier_tender_integration.md                                                                                          |   78 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java                   |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java                              |   27 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java                        |   45 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java              |   72 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java                                  |   10 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java             |   12 
 approval-detail-before-full.png                                                                                                  |    0 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java               |  104 +
 docs/erp_purchase_invoice_remove_audit.md                                                                                        |   70 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java                            |    7 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java                        |    6 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java                 |    9 
 docs/sql/erp_purchase_invoice_remove_audit.sql                                                                                   |    9 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java                  |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/meteringcheck/MesDvMeteringCheckMapper.java              |   43 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java                      |  119 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringStatusEnum.java                                 |   38 
 docs/mes_item_status_control.md                                                                                                  |   58 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java                       |   13 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java                   |   67 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringSaveReqVO.java           |   61 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/metering/MesDvMeteringDO.java                       |  100 +
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java                                         |    4 
 yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml                                                         |   65 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java             |    4 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java                  |   31 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java         |   55 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java                                          |    3 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java            |   39 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckRespVO.java    |   74 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java               |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java   |  130 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java                               |   35 
 docs/projectSql/ruoyi-vue-pro.sql                                                                                                |    2 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java    |   14 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java                |    5 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java                        |   40 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java  |    4 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java                         |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java                |   54 
 sql/mysql/mes_dv_metering_init.sql                                                                                               |  119 +
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java            |   22 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java          |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java                     |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringCheckResultEnum.java                            |   37 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java     |   37 
 contract-approve-detail.png                                                                                                      |    0 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java           |   47 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java                                         |    7 
 approval-detail-before.png                                                                                                       |    0 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java          |   22 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckSaveReqVO.java |   48 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java                       |   25 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java    |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java                             |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringExpireStatusEnum.java                           |   61 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoicePageReqVO.java |    6 
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java                             |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemController.java                     |   14 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java              |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java                      |   23 
 docs/purchase_invoice_payment_link.md                                                                                            |   49 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java               |   13 
 docs/batch_trace_extended_frontend.md                                                                                            |  305 +++
 docs/contract_generate_order_frontend.md                                                                                         |    3 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java                          |   75 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java        |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringPageReqVO.java           |   47 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java                     |  181 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringRespVO.java              |  103 +
 docs/pd_project_bpm_approval_frontend.md                                                                                         |   87 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckServiceImpl.java           |  165 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java                    |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                         |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java                                      |   16 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java                              |    5 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java     |    5 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java                    |   55 
 yudao-server/src/main/resources/application-yzfx.yaml                                                                            |  324 +++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckService.java               |   69 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchProcessTraceRespVO.java        |   46 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchPurchaseSourceRespVO.java      |   52 
 .playwright-mcp/page-2026-08-14T05-18-36-135Z.yml                                                                                |    1 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java                                  |    1 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java                       |   30 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java                   |   97 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/meteringcheck/MesDvMeteringCheckDO.java             |   73 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java                         |  109 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceDO.java                     |   13 
 docs/srm_certificate_attachment_integration.md                                                                                   |  113 +
 .playwright-mcp/page-2026-08-14T05-18-50-100Z.yml                                                                                |    1 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java                       |  121 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindService.java                   |   17 
 docs/mes_dv_metering_frontend_integration.md                                                                                     |  116 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java               |   17 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java                                |    4 
 yudao-module-srm/src/main/resources/sql/srm_init.sql                                                                             |    4 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java                          |   46 
 .playwright-mcp/page-2026-08-14T05-19-08-995Z.yml                                                                                |  103 +
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java                               |    5 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java    |   12 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java                                       |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java                             |    4 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java                                           |    4 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java     |   36 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java                           |   43 
 /dev/null                                                                                                                        |   34 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java             |  145 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java                           |   24 
 approval-detail-v2.jpeg                                                                                                          |    0 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java                            |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java                         |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckPageReqVO.java |   29 
 116 files changed, 4,694 insertions(+), 285 deletions(-)

diff --git a/.playwright-mcp/page-2026-08-14T05-18-36-135Z.yml b/.playwright-mcp/page-2026-08-14T05-18-36-135Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-14T05-18-36-135Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-14T05-18-50-100Z.yml b/.playwright-mcp/page-2026-08-14T05-18-50-100Z.yml
new file mode 100644
index 0000000..6adf37e
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-14T05-18-50-100Z.yml
@@ -0,0 +1 @@
+- generic [active]: 宸ヤ笟AI鏅洪�犲钩鍙�
\ No newline at end of file
diff --git a/.playwright-mcp/page-2026-08-14T05-19-08-995Z.yml b/.playwright-mcp/page-2026-08-14T05-19-08-995Z.yml
new file mode 100644
index 0000000..8384a0a
--- /dev/null
+++ b/.playwright-mcp/page-2026-08-14T05-19-08-995Z.yml
@@ -0,0 +1,103 @@
+- generic [ref=f1e7]:
+  - complementary [ref=f1e8]:
+    - generic [ref=f1e9]:
+      - generic [ref=f1e10] [cursor=pointer]
+      - link "宸ヤ笟AI鏅洪�犲钩鍙� 宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f1e15] [cursor=pointer]:
+        - /url: javascript:void 0
+        - img "宸ヤ笟AI鏅洪�犲钩鍙�" [ref=f1e18]
+        - generic [ref=f1e19]: 宸ヤ笟AI鏅洪�犲钩鍙�
+      - menu [ref=f1e22]:
+        - menuitem "棣栭〉" [ref=f1e23] [cursor=pointer]
+        - listitem [ref=f1e30]:
+          - generic [ref=f1e31] [cursor=pointer]: 鍩虹鏁版嵁
+        - listitem [ref=f1e39]:
+          - generic [ref=f1e40] [cursor=pointer]: 閲囪喘绠$悊
+        - listitem [ref=f1e46]:
+          - generic [ref=f1e47] [cursor=pointer]: 钀ラ攢绠$悊
+        - listitem [ref=f1e53]:
+          - generic [ref=f1e54] [cursor=pointer]: 璁惧绠$悊
+        - listitem [ref=f1e61]:
+          - generic [ref=f1e62] [cursor=pointer]: 浠撳偍鐗╂祦
+        - listitem [ref=f1e70]:
+          - generic [ref=f1e71] [cursor=pointer]: 鐢熶骇绠℃帶
+        - listitem [ref=f1e78]:
+          - generic [ref=f1e79] [cursor=pointer]: 璐ㄩ噺绠$悊
+        - listitem [ref=f1e85]:
+          - generic [ref=f1e86] [cursor=pointer]: 鍐崇瓥BI澶у睆
+        - listitem [ref=f1e92]:
+          - generic [ref=f1e93] [cursor=pointer]: 鍗忓悓鍔炲叕
+          - list [ref=f1e99]:
+            - listitem [ref=f1e100]:
+              - generic [ref=f1e101] [cursor=pointer]: 娴佺▼绠$悊
+              - list [ref=f1e107]:
+                - menuitem "娴佺▼妯″瀷" [ref=f1e108] [cursor=pointer]
+                - menuitem "娴佺▼琛ㄥ崟" [ref=f1e113] [cursor=pointer]
+                - menuitem "娴佺▼鍒嗙被" [ref=f1e118] [cursor=pointer]
+                - menuitem "鐢ㄦ埛鍒嗙粍" [ref=f1e123] [cursor=pointer]
+                - menuitem "娴佺▼鐩戝惉鍣�" [ref=f1e128] [cursor=pointer]
+                - menuitem "娴佺▼琛ㄨ揪寮�" [ref=f1e133] [cursor=pointer]
+                - menuitem "娴佺▼瀹炰緥" [ref=f1e138] [cursor=pointer]
+                - menuitem "娴佺▼浠诲姟" [ref=f1e143] [cursor=pointer]
+            - listitem [ref=f1e148]:
+              - generic [ref=f1e149] [cursor=pointer]: 瀹℃壒涓績
+            - menuitem "API瀵嗛挜绠$悊" [ref=f1e155] [cursor=pointer]
+            - menuitem "妯″瀷绠$悊" [ref=f1e161] [cursor=pointer]
+            - menuitem "鐭ヨ瘑搴撶鐞�" [ref=f1e166] [cursor=pointer]
+            - menuitem "閫氱煡鍏憡" [ref=f1e172] [cursor=pointer]
+        - listitem [ref=f1e177]:
+          - generic [ref=f1e178] [cursor=pointer]: 浜哄姏璧勬簮
+        - listitem [ref=f1e184]:
+          - generic [ref=f1e185] [cursor=pointer]: 鍞悗绠$悊
+        - listitem [ref=f1e191]:
+          - generic [ref=f1e192] [cursor=pointer]: 鍩虹璁炬柦
+        - listitem [ref=f1e198]:
+          - generic [ref=f1e199] [cursor=pointer]: 绯荤粺绠$悊
+        - listitem [ref=f1e205]:
+          - generic [ref=f1e206] [cursor=pointer]: IM 鍗虫椂閫氳
+      - generic [ref=f1e214] [cursor=pointer]
+  - generic [ref=f1e219]:
+    - generic [ref=f1e220]:
+      - banner [ref=f1e221]:
+        - button [ref=f1e222] [cursor=pointer]
+        - button [ref=f1e225] [cursor=pointer]
+        - navigation "breadcrumb" [ref=f1e230]:
+          - list [ref=f1e231]:
+            - listitem [ref=f1e232]:
+              - link "鍗忓悓鍔炲叕" [ref=f1e233] [cursor=pointer]:
+                - /url: javascript:void 0
+              - listitem [ref=f1e237]
+            - listitem [ref=f1e240]:
+              - link "娴佺▼绠$悊" [ref=f1e241] [cursor=pointer]:
+                - /url: javascript:void 0
+              - listitem [ref=f1e245]
+            - listitem [ref=f1e248]:
+              - link "娴佺▼妯″瀷" [disabled] [ref=f1e249]
+        - generic [ref=f1e254]:
+          - generic [ref=f1e256] [cursor=pointer]:
+            - generic [ref=f1e260]: 鎼滅储
+            - generic [ref=f1e261]: Ctrl K
+          - button [ref=f1e263] [cursor=pointer]
+          - button "dark" [ref=f1e268] [cursor=pointer]
+          - button [ref=f1e277] [cursor=pointer]
+          - button [ref=f1e283] [cursor=pointer]
+          - button [ref=f1e286] [cursor=pointer]
+          - button [ref=f1e292] [cursor=pointer]:
+            - button [ref=f1e294]
+          - button "宸ヤ綔鍙�" [ref=f1e299] [cursor=pointer]
+          - button [ref=f1e303] [cursor=pointer]:
+            - img "瓒呯骇绠$悊鍛�" [ref=f1e308]
+      - generic [ref=f1e310]:
+        - generic [ref=f1e315]:
+          - generic [ref=f1e316] [cursor=pointer]: 棣栭〉
+          - generic [ref=f1e334] [cursor=pointer]: 娴佺▼妯″瀷
+        - generic [ref=f1e351]:
+          - button [ref=f1e352] [cursor=pointer]
+          - generic [ref=f1e359] [cursor=pointer]
+          - generic [ref=f1e363] [cursor=pointer]
+    - main [ref=f1e370]:
+      - generic [ref=f1e376]:
+        - generic [ref=f1e377]: 娴佺▼妯″瀷
+        - generic [ref=f1e379]:
+          - textbox "鎼滅储娴佺▼" [ref=f1e381]
+          - button "鏂板缓妯″瀷" [ref=f1e383] [cursor=pointer]
+          - button [ref=f1e387] [cursor=pointer]
\ No newline at end of file
diff --git a/approval-detail-before-full.png b/approval-detail-before-full.png
new file mode 100644
index 0000000..8173a54
--- /dev/null
+++ b/approval-detail-before-full.png
Binary files differ
diff --git a/approval-detail-before.png b/approval-detail-before.png
new file mode 100644
index 0000000..d7d7e55
--- /dev/null
+++ b/approval-detail-before.png
Binary files differ
diff --git a/approval-detail-v2.jpeg b/approval-detail-v2.jpeg
new file mode 100644
index 0000000..6c26c21
--- /dev/null
+++ b/approval-detail-v2.jpeg
Binary files differ
diff --git a/contract-approve-detail.png b/contract-approve-detail.png
new file mode 100644
index 0000000..723c4c5
--- /dev/null
+++ b/contract-approve-detail.png
Binary files differ
diff --git a/docs/batch_trace_extended_frontend.md b/docs/batch_trace_extended_frontend.md
new file mode 100644
index 0000000..8a04534
--- /dev/null
+++ b/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 涓帴鍙e嚱鏁� |
+| 鍒楀畾涔� | `src/views/mes/qc/batchtrace/data.ts` | 鏂板 3 缁勫垪閰嶇疆 |
+
+---
+
+## 浜屻�丄PI 鎺ュ彛
+
+### 2.1 鎵规閲囪喘鏉ユ簮
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/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": "渚涘簲鍟咥",
+      "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 鎵规閿�鍞幓鍚�
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/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 鎵规宸ュ簭娴佽浆
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/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 | 鏁伴噺锛圤UT=浜у嚭鏁伴噺锛孖N=鎶曞叆鏁伴噺锛� |
+| qualityStatus | Integer | 璐ㄩ噺鐘舵�侊紙浠� OUT 渚ф湁鍊硷紝IN 渚т负 null锛� |
+| eventTime | Date | 浜嬩欢鏃堕棿锛圤UT=浜у嚭鏃ユ湡锛孖N=娑堣�楁棩鏈燂級 |
+| processSort | Integer | 宸ュ簭搴忓彿锛堢敤浜庡睍绀哄伐搴忓厛鍚庯級 |
+
+**鎺掑簭瑙勫垯**锛氬悗绔凡鎸� `workOrderId asc 鈫� processSort asc锛堢┖鍊肩疆鍚庯級鈫� eventTime asc` 鎺掑簭锛屽墠绔洿鎺ユ寜杩斿洖椤哄簭灞曠ず鍗冲彲銆�
+
+### 2.4 SN 鍙嶆煡鎵规
+
+**璇锋眰鏂瑰紡**锛欸ET
+
+**璇锋眰璺緞**锛歚/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": "渚涘簲鍟咥",
+    "clientName": "瀹㈡埛A",
+    "workOrderCode": "WO20260730001",
+    "taskCode": "TASK001",
+    "workstationCode": "WS001",
+    "toolCode": "TOOL001",
+    "lotNumber": "LOT20260801"
+  }
+}
+```
+
+**璇存槑**锛歋N 鍦� `mes_wm_sn` 琛ㄩ�氳繃 `code` 鍞竴鍖归厤锛屽懡涓悗鍙栧叾 `batch_code` 鍙嶆煡鎵规銆傚墠绔牴鎹� `data` 鏄惁涓虹┖鍒ゆ柇鏄惁鎻愮ず"鏈壘鍒板搴旀壒娆�"銆�
+
+---
+
+## 涓夈�佸瓧娈靛睍绀鸿鍒�
+
+| 鍔熻兘 | 灞曠ず浣嶇疆 | 瀛楁璇存槑 |
+|------|----------|----------|
+| 閲囪喘鏉ユ簮 | 璇︽儏寮圭獥瀛愰〉绛�"閲囪喘鏉ユ簮" | 鍏ュ簱鍗曞彿銆佸叆搴撴棩鏈熴�侀噰璐鍗曘�佷緵搴斿晢銆両QC鍗曞彿銆両QC缁撹銆佺墿鏂欍�佹暟閲� |
+| 閿�鍞幓鍚� | 璇︽儏寮圭獥瀛愰〉绛�"閿�鍞幓鍚�" | 鍑哄簱鍗曞彿銆佸嚭搴撴棩鏈熴�侀攢鍞鍗曘�佸鎴枫�丱QC鍗曞彿銆丱QC缁撹銆佺墿鏂欍�佹暟閲忋�佽川閲忕姸鎬� |
+| 宸ュ簭娴佽浆 | 璇︽儏寮圭獥瀛愰〉绛�"宸ュ簭娴佽浆" | 浜嬩欢绫诲瀷銆佸伐鍗曞彿銆佸伐搴忕紪鐮�/鍚嶇О銆佹暟閲忋�佹椂闂淬�佽川閲忕姸鎬� |
+| 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` 浣滀负璇︽儏寮圭獥鏌ヨ渚濇嵁锛堟垨鐩存帴浣跨敤杩斿洖鏁版嵁锛� |
+
+---
+
+## 浜斻�佹帴鍙h皟鐢ㄦ椂搴�
+
+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 瀵瑰簲鐨勬壒娆�" |
+| 鏁版嵁涓虹┖ | 涓変釜瀛愬垪琛ㄦ帴鍙e潎鍙兘杩斿洖绌烘暟缁勶紝椤甸潰灞曠ず绌烘�佹彁绀� |
+
+---
+
+## 涓冦�佹敞鎰忎簨椤�
+
+- 涓変釜鍒楄〃鎺ュ彛涓庢棦鏈� forward/backward 鐩稿悓锛屽潎闇�瑕� `mes:wm-batch:query` 鏉冮檺
+- 瀛愰〉绛惧缓璁噿鍔犺浇 + 缂撳瓨锛岄伩鍏嶆瘡娆″垏鎹㈤噸澶嶈姹�
+- 宸ュ簭娴佽浆鐨� `processSort` 鐢卞悗绔帓濂藉簭锛屽墠绔嬁鍐嶆帓搴�
+- 閿�鍞晶 `qualityStatus` 涓哄瓧鍏稿�硷紝灞曠ず鐢� `mes_wm_quality_status` 瀛楀吀缈昏瘧
+- 娴嬭瘯鐜鍙兘瀛樺湪鏃� `batchCode` 鐨勯攢鍞嚭搴撹鏁版嵁锛屽睘鍘嗗彶鑴忔暟鎹紝鍓嶇瀵圭┖瀛楁鍋氬厹搴曞睍绀哄嵆鍙�
+
+---
+
+## 鍏�佺浉鍏虫帴鍙f眹鎬�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| 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 鍙嶆煡鎵规 |
diff --git a/docs/contract_generate_order_frontend.md b/docs/contract_generate_order_frontend.md
index cab4848..5ad5506 100644
--- a/docs/contract_generate_order_frontend.md
+++ b/docs/contract_generate_order_frontend.md
@@ -228,6 +228,9 @@
 ALTER TABLE `erp_sale_order`
 ADD COLUMN `contract_id` bigint DEFAULT NULL COMMENT '鍏宠仈 CRM 鍚堝悓缂栧彿' AFTER `customer_id`,
 ADD COLUMN `contract_no` varchar(32) DEFAULT NULL COMMENT 'CRM 鍚堝悓鍗曞彿' AFTER `contract_id`;
+
+ALTER TABLE `erp_sale_order`
+MODIFY COLUMN `deposit_price` decimal(24,6) NOT NULL DEFAULT 0 COMMENT '瀹氶噾閲戦锛屽崟浣嶏細鍏�';
 ```
 
 ---
diff --git a/docs/erp_purchase_invoice_remove_audit.md b/docs/erp_purchase_invoice_remove_audit.md
new file mode 100644
index 0000000..bbc75db
--- /dev/null
+++ b/docs/erp_purchase_invoice_remove_audit.md
@@ -0,0 +1,70 @@
+# ERP 閲囪喘鏉ョエ鍘绘帀瀹℃牳 - 鍓嶇鑱旇皟鏂规
+
+## 鍙樻洿鑳屾櫙
+
+閲囪喘鏉ョエ锛坄erp_purchase_invoice`锛変笉鍐嶉渶瑕佸鏍告祦绋嬶紝鏉ョエ鍒涘缓鍚庣洿鎺ョ敓鏁堛�佸彲鐩存帴鐢ㄤ簬浠樻銆傚悗绔凡鍒犻櫎瀹℃牳鐘舵�佸瓧娈碉紙`auditStatus`锛変笌宸ヤ綔娴佹畫鐣欏瓧娈碉紙`processInstanceId`锛夛紝骞剁Щ闄や簡瀹℃牳鐩稿叧鎺ュ彛锛涗粯娆惧垱寤烘椂涓嶅啀鏍¢獙"鏉ョエ蹇呴』瀹℃牳閫氳繃"銆�
+
+## 娑夊強椤甸潰
+
+- 閲囪喘鏉ョエ绠$悊椤碉紙`views/erp/purchase/invoice/`锛�
+- 浠樻鍗曠鐞嗛〉锛坄views/erp/finance/payment/`锛屼粎琛屼负鍙樺寲锛屾棤闇�鏀逛唬鐮侊級
+
+## 鎺ュ彛鍙樻洿
+
+### 宸插垹闄ゆ帴鍙�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT | /erp/purchase-invoice/update-status | 瀹℃牳/鍙嶅鏍革紝鍓嶇闇�鍒犻櫎瀵瑰簲鎸夐挳涓� API 鍑芥暟 |
+| GET | /erp/purchase-invoice/audit-count | 寰呭鏍告潵绁ㄦ暟閲忥紝鍓嶇闇�鍒犻櫎瀵瑰簲 API 鍑芥暟 |
+
+### 鍝嶅簲瀛楁鍙樺寲
+
+| 鎺ュ彛 | 鍙樺寲 |
+|------|------|
+| /erp/purchase-invoice/page | 鍝嶅簲绉婚櫎 `auditStatus`銆乣processInstanceId` 瀛楁 |
+| /erp/purchase-invoice/get | 鍝嶅簲绉婚櫎 `auditStatus`銆乣processInstanceId` 瀛楁 |
+| /erp/purchase-invoice/simple-list | 鍝嶅簲绉婚櫎 `auditStatus` 瀛楁锛涗笉鍐嶅彧杩斿洖"瀹℃牳閫氳繃"鏉ョエ锛屾敼涓鸿繑鍥炶渚涘簲鍟�/璁㈠崟涓�"宸蹭笂浼犻檮浠朵笖鍓╀綑鍙粯 > 0"鐨勫叏閮ㄦ潵绁� |
+| /erp/finance-payment/get銆�/page | 宓屽 `invoice` 瀵硅薄涓笉鍐嶅寘鍚� `auditStatus` 瀛楁 |
+
+### 鏍¢獙瑙勫垯鍙樺寲锛堝悗绔紝鍓嶇鏃犳敼鍔級
+
+| 鍦烘櫙 | 鍙樺寲 |
+|------|------|
+| 浠樻鍒涘缓/鏇存柊 | 鍘绘帀"鍏宠仈鏉ョエ蹇呴』瀹℃牳閫氳繃"鏍¢獙锛屽叾浣欐牎楠岋紙鏉ョエ瀛樺湪銆佸凡浼犻檮浠躲�佷緵搴斿晢涓�鑷淬�侀噾棰濅笉瓒呭墿浣欏彲浠橈級淇濈暀 |
+
+## 鍓嶇淇敼鐐�
+
+### 1. API 瀹氫箟锛坄src/api/erp/purchase/invoice/index.ts`锛�
+
+- `PurchaseInvoice` 绫诲瀷锛氬垹闄� `auditStatus`銆乣processInstanceId` 瀛楁
+- `PurchaseInvoiceSimple` 绫诲瀷锛氬垹闄� `auditStatus` 瀛楁
+- 鍒犻櫎鍑芥暟锛歚updatePurchaseInvoiceStatus`銆乣getAuditPurchaseInvoiceCount`
+- 鏇存柊 `getPurchaseInvoiceSimpleList` 娉ㄩ噴璇存槑
+
+### 2. 鏉ョエ鍒楄〃椤碉紙`src/views/erp/purchase/invoice/index.vue`锛�
+
+- 鍒犻櫎"瀹℃牳""鍙嶅鏍�"鎿嶄綔鎸夐挳鍙婂搴斿鐞嗗嚱鏁�
+- 鍒犻櫎"瀹℃壒鐘舵��"鍒楃殑鐘舵�� Tag 妯℃澘
+- 缂栬緫鎸夐挳涓嶅啀鎸夊鏍哥姸鎬佹帶鍒舵樉闅愶紙浠绘剰鐘舵�佸彲缂栬緫锛�
+- 鍒犻櫎鎸夐挳涓嶅啀鎸夊鏍哥姸鎬佹帶鍒舵樉闅愶紙鏄惁鍙垹鐢卞悗绔牎楠�"宸叉湁鏈夋晥浠樻鍗�"鍐冲畾锛�
+
+### 3. 鏉ョエ鍒楄〃閰嶇疆锛坄src/views/erp/purchase/invoice/data.ts`锛�
+
+- 鎼滅储琛ㄥ崟锛氬垹闄�"瀹℃牳鐘舵��"绛涢�夐」
+- 鍒楄〃鍒楋細鍒犻櫎"瀹℃壒鐘舵��"鍒�
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鏉ョエ鍒涘缓 | 鍒涘缓鍗崇敓鏁堬紝鏃犲鏍哥幆鑺� |
+| 鏉ョエ缂栬緫 | 浠绘剰鐘舵�佸彲缂栬緫 |
+| 鏉ョエ鍒犻櫎 | 宸叉湁鏈夋晥浠樻鍗曪紙浠樻閲戦 > 0锛夊叧鑱旂殑鏉ョエ涓嶅彲鍒犻櫎锛屽悗绔細杩斿洖閿欒鎻愮ず |
+| 浠樻閫夋嫨鏉ョエ | 涓嬫媺鍙睍绀�"宸蹭笂浼犻檮浠朵笖鍓╀綑鍙粯 > 0"鐨勬潵绁紝涓嶅啀鏈�"寰呭鏍�"鏉ョエ |
+
+## 娉ㄦ剰浜嬮」
+
+- 浠樻妯″潡锛坄views/erp/finance/payment/`锛夋湭寮曠敤 `auditStatus`锛屾棤闇�淇敼锛涘叾"鍏宠仈鏉ョエ"涓嬫媺鍙�夎寖鍥撮殢鍚庣 simple-list 閫昏緫鑷姩鍙樺寲銆�
+- 鍘嗗彶鏉ョエ鏁版嵁锛氭暟鎹簱宸插垹闄� `audit_status`銆乣process_instance_id` 涓ゅ垪锛屽巻鍙叉潵绁ㄥ叏閮ㄨ涓烘湁鏁堬紝鍙洿鎺ョ敤浜庝粯娆俱��
+- 鍓嶇鎺ュ彛鍒犻櫎鍚庯紝璇峰悓姝ユ鏌ユ槸鍚︽湁鍏跺畠椤甸潰锛堝宸ヤ綔鍙�/寰呭姙锛夎皟鐢ㄥ凡鍒犻櫎鐨勬帴鍙o紝褰撳墠鏈彂鐜般��
diff --git a/docs/mes_dv_metering_frontend_integration.md b/docs/mes_dv_metering_frontend_integration.md
new file mode 100644
index 0000000..75dddc8
--- /dev/null
+++ b/docs/mes_dv_metering_frontend_integration.md
@@ -0,0 +1,116 @@
+# MES 璁¢噺鍣ㄥ叿涓庤閲忔娴嬭褰� - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+| 椤甸潰 | 鑿滃崟璺緞 | 璺敱璺緞 | component_name |
+|------|----------|----------|----------------|
+| 璁¢噺鍣ㄥ叿 | 璁惧绠$悊 -> 璁¢噺鍣ㄥ叿 | `mes/dv/metering/index` | MesDvMetering |
+| 璁¢噺妫�娴嬭褰� | 璁惧绠$悊 -> 璁¢噺妫�娴嬭褰� | `mes/dv/meteringcheck/index` | MesDvMeteringCheck |
+
+> 涓や釜椤甸潰涓哄钩绾ц彍鍗曘�傛娴嬭褰曢〉鍙�氳繃銆岃閲忓櫒鍏枫�嶄笅鎷夌瓫閫夛紝浠呮煡鐪嬫煇鍙板櫒鍏风殑妫�娴嬪巻鍙层��
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 璁¢噺鍣ㄥ叿鍒楄〃 -> 鏂板/缂栬緫琛ㄥ崟锛氬綍鍏ュ櫒鍏风紪鐮併�佸悕绉般�佺被鍒�佹牎鍑嗗懆鏈熴�佷娇鐢ㄩ儴闂ㄣ�佽矗浠讳汉绛夈��
+2. 璁¢噺妫�娴嬭褰� -> 鏂板/缂栬緫琛ㄥ崟锛氶�夋嫨銆岃閲忓櫒鍏枫�嶏紙浠� `/mes/dv/metering/simple-list` 甯﹀叆 `meteringId`銆乣code`銆乣name`锛夈��
+3. 妫�娴嬭褰曚繚瀛樺悗锛屽悗绔嚜鍔ㄨ绠� `nextCheckDate` 骞�**鍥炲啓**鍒拌閲忓櫒鍏风殑 `lastCheckDate`銆乣nextCheckDate`锛�
+   - `nextCheckDate` 浼犵┖鏃� = `checkDate + 鏍″噯鍛ㄦ湡(鏈�)`锛堣嚜鍔ㄨ绠楋級锛�
+   - `nextCheckDate` 浼犲�兼椂 = 浠ヤ紶鍏ュ�间负鍑嗭紙鎵嬪姩鎸囧畾锛夈��
+4. 璁¢噺鍣ㄥ叿鍒楄〃灞曠ず `expireStatus` 鍒版湡鐘舵�佹爣璁帮紝鐢卞悗绔寜 `nextCheckDate` 涓庡叏灞�閰嶇疆鐨勬彁閱掑ぉ鏁拌绠椼��
+
+## API
+
+### 璁¢噺鍣ㄥ叿
+
+| 鏂规硶 | 璺緞 | 璇存槑 | 鏉冮檺 |
+|------|------|------|------|
+| POST | /mes/dv/metering/create | 鍒涘缓璁¢噺鍣ㄥ叿 | mes:dv-metering:create |
+| PUT | /mes/dv/metering/update | 鏇存柊璁¢噺鍣ㄥ叿 | mes:dv-metering:update |
+| DELETE | /mes/dv/metering/delete?id= | 鍒犻櫎璁¢噺鍣ㄥ叿锛堟湁鍏宠仈妫�娴嬭褰曟椂鎷掔粷锛� | mes:dv-metering:delete |
+| GET | /mes/dv/metering/get?id= | 鑾峰緱璁¢噺鍣ㄥ叿璇︽儏 | mes:dv-metering:query |
+| GET | /mes/dv/metering/page | 璁¢噺鍣ㄥ叿鍒嗛〉 | mes:dv-metering:query |
+| GET | /mes/dv/metering/export-excel | 瀵煎嚭 Excel | mes:dv-metering:export |
+| GET | /mes/dv/metering/simple-list | 绮剧畝鍒楄〃锛堜笅鎷夐�夐」锛� | mes:dv-metering:query |
+
+**鍒嗛〉绛涢�夊弬鏁帮紙page 鎺ュ彛锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| code | String | 鍚� | 鍣ㄥ叿缂栫爜妯$硦 |
+| name | String | 鍚� | 鍣ㄥ叿鍚嶇О妯$硦 |
+| brand | String | 鍚� | 鍝佺墝/鐢熶骇鍘傚妯$硦 |
+| category | Integer | 鍚� | 鍣ㄥ叿绫诲埆锛堝瓧鍏革級 |
+| deptId | Long | 鍚� | 浣跨敤閮ㄩ棬 |
+| chargeUserId | Long | 鍚� | 璐d换浜� |
+| status | Integer | 鍚� | 鐘舵�侊紙瀛楀吀锛� |
+| expireStatus | Integer | 鍚� | 鍒版湡鐘舵�侊細1 宸茶繃鏈� / 2 鍗冲皢鍒版湡 / 3 姝e父 |
+
+### 璁¢噺妫�娴嬭褰�
+
+| 鏂规硶 | 璺緞 | 璇存槑 | 鏉冮檺 |
+|------|------|------|------|
+| POST | /mes/dv/metering-check/create | 鍒涘缓妫�娴嬭褰� | mes:dv-metering-check:create |
+| PUT | /mes/dv/metering-check/update | 鏇存柊妫�娴嬭褰� | mes:dv-metering-check:update |
+| DELETE | /mes/dv/metering-check/delete?id= | 鍒犻櫎妫�娴嬭褰� | mes:dv-metering-check:delete |
+| GET | /mes/dv/metering-check/get?id= | 鑾峰緱妫�娴嬭褰曡鎯� | mes:dv-metering-check:query |
+| GET | /mes/dv/metering-check/page | 妫�娴嬭褰曞垎椤� | mes:dv-metering-check:query |
+| GET | /mes/dv/metering-check/export-excel | 瀵煎嚭 Excel | mes:dv-metering-check:export |
+
+**鍒嗛〉绛涢�夊弬鏁帮紙page 鎺ュ彛锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| meteringId | Long | 鍚� | 璁¢噺鍣ㄥ叿锛堜粠鍣ㄥ叿鍒楄〃甯﹀叆锛� |
+| checkCode | String | 鍚� | 妫�娴嬬紪鍙锋ā绯� |
+| checkResult | Integer | 鍚� | 妫�娴嬬粨璁猴紙瀛楀吀锛� |
+| checkDate | LocalDate[] | 鍚� | 妫�娴嬫棩鏈熷尯闂� |
+
+### 鍝嶅簲瀛楁鏂板璇存槑
+
+璁¢噺鍣ㄥ叿鍒楄〃/璇︽儏鍝嶅簲棰濆杩斿洖锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| deptName | String | 浣跨敤閮ㄩ棬鍚嶇О |
+| chargeUserNickname | String | 璐d换浜烘樀绉� |
+| expireStatus | Integer | 鍒版湡鐘舵�侊細1 宸茶繃鏈� / 2 鍗冲皢鍒版湡 / 3 姝e父 |
+| expireDays | Long | 璺濆埌鏈熷ぉ鏁帮紙姝f暟鏈埌銆佽礋鏁板凡杩囨湡锛� |
+
+璁¢噺妫�娴嬭褰曞垪琛�/璇︽儏鍝嶅簲棰濆杩斿洖锛�
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| meteringCode | String | 璁¢噺鍣ㄥ叿缂栫爜 |
+| meteringName | String | 璁¢噺鍣ㄥ叿鍚嶇О |
+| checkUserNickname | String | 妫�娴嬩汉鏄电О |
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| expireStatus | 璁¢噺鍣ㄥ叿鍒楄〃 | 1 宸茶繃鏈�=鍗遍櫓(danger)锛�2 鍗冲皢鍒版湡=璀﹀憡(warning)锛�3 姝e父=鎴愬姛(success) |
+| status | 璁¢噺鍣ㄥ叿鍒楄〃銆佽〃鍗� | 瀛楀吀 mes_dv_metering_status锛堝湪鐢�=success/鍋滅敤=info/鎶ュ簾=danger锛� |
+| category | 璁¢噺鍣ㄥ叿鍒楄〃銆佽〃鍗� | 瀛楀吀 mes_dv_metering_category |
+| nextCheckDate | 涓や釜椤甸潰 | 璁¢噺鍣ㄥ叿灞曠ず涓恒�屼笅娆℃娴嬫棩鏈熴�嶅垪锛涙娴嬭褰曚繚瀛樻椂鍥炲啓 |
+| checkResult | 妫�娴嬭褰曞垪琛ㄣ�佽〃鍗� | 瀛楀吀 mes_dv_metering_check_result锛堝悎鏍�=success/涓嶅悎鏍�=danger锛� |
+| meteringCode/meteringName | 妫�娴嬭褰曞垪琛� | 浠庡櫒鍏峰甫鍏ュ睍绀� |
+| deptName/chargeUserNickname | 璁¢噺鍣ㄥ叿鍒楄〃 | 閮ㄩ棬銆佽矗浠讳汉鍚嶇О |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 妫�娴嬭褰曟柊澧� | 鏈~ nextCheckDate 鏃舵寜 `checkDate + 鏍″噯鍛ㄦ湡` 鑷姩璁$畻骞朵繚瀛� |
+| 妫�娴嬭褰曟洿鏂� | 閲嶆柊璁$畻 nextCheckDate锛屽苟鍥炲啓鍣ㄥ叿 |
+| 妫�娴嬭褰曞垹闄� | 鍣ㄥ叿鎸夊墿浣欍�屾渶鏂颁竴鏉¤褰曘�嶆暣浣撻噸绠楀洖鍐欙紙鏃犺褰曞垯 lastCheckDate/nextCheckDate 缃┖锛� |
+| 璁¢噺鍣ㄥ叿鍒犻櫎 | 宸插叧鑱旀娴嬭褰曟椂鎷掔粷鍒犻櫎锛屾彁绀恒�岃閲忓櫒鍏峰凡鍏宠仈妫�娴嬭褰曪紝鏃犳硶鍒犻櫎銆� |
+| 鍒版湡鎻愰啋 | 鍒版湡鍓� N 澶╋紙鍏ㄥ眬閰嶇疆 `mes.dv-metering.remind-days`锛岄粯璁� 30锛夊彂閫佺珯鍐呬俊缁欒矗浠讳汉锛涘悓涓�鍣ㄥ叿鍚屼竴鍒版湡鏃� 60 澶╁唴鍙彁閱掍竴娆� |
+| 缂栫爜/缂栧彿鍞竴 | 鍣ㄥ叿 code銆佹娴� checkCode 鍧囧敮涓�锛岄噸澶嶆椂鍚庣鎶ラ敊 |
+
+## 娉ㄦ剰浜嬮」
+
+- expireStatus 鐢卞悗绔绠楀苟杩斿洖锛屽墠绔彧闇�鎸変笁鎬佹覆鏌� tag 棰滆壊锛屾棤闇�鑷璁$畻澶╂暟銆�
+- 鍒犻櫎鍣ㄥ叿鍓嶉渶纭鏃犲叧鑱旀娴嬭褰曪紱鍒犻櫎妫�娴嬭褰曞悗鍣ㄥ叿鐨勫埌鏈熸棩浼氶殢涔嬪彉鍖栵紝鍓嶇搴斿埛鏂板櫒鍏峰垪琛ㄣ��
+- 妫�娴嬭褰曢〉渚濊禆璁¢噺鍣ㄥ叿鏁版嵁锛屽簲鍏堝瓨鍦ㄥ櫒鍏峰啀鐧昏妫�娴嬭褰曘��
+- 鎻愰啋澶╂暟鍙湪銆屽熀纭�璁炬柦 -> 閰嶇疆绠$悊銆嶄腑淇敼 `mes.dv-metering.remind-days`锛屾敼鍚庣珛鍗崇敓鏁堛��
+- 涓変釜鏂板瓧鍏稿彲鍦ㄣ�岀郴缁熺鐞� -> 瀛楀吀绠$悊銆嶄腑缁存姢锛歮es_dv_metering_category銆乵es_dv_metering_status銆乵es_dv_metering_check_result銆�
diff --git a/docs/mes_item_status_control.md b/docs/mes_item_status_control.md
new file mode 100644
index 0000000..fa98410
--- /dev/null
+++ b/docs/mes_item_status_control.md
@@ -0,0 +1,58 @@
+# MES 鐗╂枡鐘舵�佹帶鍒� - 鍓嶇鑱旇皟鏂规
+
+## 鍙樻洿姒傝堪
+
+- MES 妯″潡**涓嶅啀鍏佽鎺у埗鐗╂枡鐘舵��**锛岀姸鎬佸彧鑳界敱鍩虹鏁版嵁锛圡DM锛夌墿鏂欑鐞嗘帶鍒躲��
+- 绉婚櫎 MES 鎺ュ彛 `PUT /mes/md/item/update-status`銆�
+- MDM 鐗╂枡鍚屾鍒� MES 鏃朵紶閫掔姸鎬侊紙鍚敤/绂佺敤锛夛紝MES 鐗╂枡鐘舵�佽窡闅� MDM 淇濇寔鍚屾銆�
+
+## 娑夊強椤甸潰
+
+- MES 鐗╂枡鍒楄〃椤碉細`src/views/mes/md/item/index.vue`锛堢Щ闄ょ姸鎬佸紑鍏筹紝鏀逛负鍙灞曠ず锛�
+- MES 鐗╂枡鏂板/缂栬緫琛ㄥ崟锛歚src/views/mes/md/item/modules/form.vue`锛堢姸鎬佸瓧娈垫敼涓虹鐢�/鍙锛�
+- 鍩虹鏁版嵁鐗╂枡绠$悊椤碉細`src/views/basicData/mdm/index.vue`锛堢姸鎬佹帶鍒跺叆鍙o紝淇濇寔涓嶅彉锛�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT锛堝凡绉婚櫎锛� | `/mes/md/item/update-status` | MES 渚х姸鎬佹帶鍒舵帴鍙o紝宸插簾寮冿紝鍓嶇**涓嶅緱鍐嶈皟鐢�** |
+
+MDM 鐘舵�佹帶鍒舵帴鍙o紙淇濇寔涓嶅彉锛岀姸鎬佷細鍚屾鍒� MES锛夛細
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/mdm/item/create` | 鍒涘缓鐗╂枡锛宻tatus 闅忓悓姝ュ甫鍏� MES |
+| PUT | `/mdm/item/update` | 鏇存柊鐗╂枡锛宻tatus 闅忓悓姝ュ甫鍏� MES |
+| PUT | `/mdm/item/update-status` | 鏇存柊鐗╂枡鐘舵�侊紝瀹炴椂鍚屾鍒� MES |
+
+## 鏁版嵁甯﹀叆璇存槑
+
+1. MDM 鍒涘缓/缂栬緫鐗╂枡鏃讹紝`status`锛�0=鍚敤锛�1=绂佺敤锛夐殢鍚屾閾捐矾甯﹀叆 MES锛孧ES 鐗╂枡鏂板缓娌跨敤 MDM 鐘舵�併��
+2. MDM 鐗╂枡鍚敤/绂佺敤锛坄update-status`锛夛紝鐘舵�佸疄鏃跺悓姝ュ埌 MES銆�
+3. MES 鏈湴鏂板鐗╂枡锛堜笉缁忚繃 MDM 鍚屾锛夐粯璁ょ姸鎬佷负**绂佺敤**锛屼笖 MES 渚ф棤娉曡嚜琛屽惎鐢紝椤诲湪 MDM 涓惎鐢ㄥ悗鍚屾鍥� MES銆�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 鐘舵�� | MES 鐗╂枡鍒楄〃 | 鍙灞曠ず锛屾潵婧愪负 MDM 鍚屾鐘舵�侊紝涓嶅啀鎻愪緵寮�鍏冲垏鎹� |
+| 鐘舵�� | MES 鏂板/缂栬緫琛ㄥ崟 | 绂佺敤/闅愯棌锛屼笉鍏佽淇敼 |
+| 鐘舵�� | MDM 鐗╂枡鍒楄〃/琛ㄥ崟 | 鍙紪杈戯紝涓虹姸鎬佸敮涓�鎺у埗鍏ュ彛 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| MDM 鍒涘缓/缂栬緫鐗╂枡 | 鐘舵�侀殢鍚屾鍒� MES锛孧ES 鐗╂枡鐘舵�佷繚鎸佷竴鑷� |
+| MDM 鍚敤/绂佺敤鐗╂枡 | 鐘舵�佸疄鏃跺悓姝ュ埌 MES |
+| MES 鏈湴鏂板鐗╂枡 | 榛樿绂佺敤锛孧ES 渚ф棤娉曞惎鐢紝椤诲湪 MDM 涓惎鐢� |
+| MES 缂栬緫鐗╂枡 | 涓嶅厑璁镐慨鏀圭姸鎬佸瓧娈� |
+| MES 鐘舵�佹帶鍒� | 涓嶅啀鎻愪緵鍚敤/绂佺敤鍏ュ彛 |
+
+## 娉ㄦ剰浜嬮」
+
+- MES 鍒楄〃椤甸渶绉婚櫎鐘舵�佸紑鍏筹紙`updateItemStatus` 鐩稿叧璋冪敤锛夛紝鐘舵�佸垪鏀逛负鍙鏂囨湰/鏍囩灞曠ず锛屾垨淇濈暀灞曠ず鍒椾絾鍘绘帀鍙垏鎹㈣兘鍔涖��
+- MES 鏂板/缂栬緫琛ㄥ崟涓殑鐘舵�佸瓧娈甸渶绂佺敤鎴栭殣钘忥紝閬垮厤璇紶瀵艰嚧鐘舵�佽淇敼銆�
+- 鍓嶇 `src/api/mes/md/item/index.ts` 涓殑 `updateItemStatus` 鍑芥暟鍙竴骞舵竻鐞嗭紙鎺ュ彛宸茬Щ闄わ級銆�
+- MDM 鍚敤鐗╂枡鏃讹紝鑻ヨ鐗╂枡鍦� MES 渚у惎鐢ㄤ簡鎵规绠$悊浣嗘湭閰嶇疆鎵规灞炴�с�佹垨涓轰骇鍝佷絾鏈厤缃� BOM锛孧ES 鍚屾鏍¢獙鍙兘鎷︽埅鍚敤鎿嶄綔锛岄渶鍏堝湪 MES 渚цˉ榻愬搴旈厤缃��
diff --git a/docs/pd_project_bpm_approval_frontend.md b/docs/pd_project_bpm_approval_frontend.md
new file mode 100644
index 0000000..f9ef944
--- /dev/null
+++ b/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) 鈫� 鐐广�屽綊妗c�嶁啋 `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 | 褰掓。锛堝緟褰掓。鈫掑凡褰掓。锛� |
+
+**璇锋眰鍙傛暟锛坰ubmit-approval锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| id | Long | 鏄� | 椤圭洰缂栧彿 |
+| processDefinitionKey | String | 鏄� | 閫変腑鐨� BPM 娴佺▼瀹氫箟 Key |
+
+**鍝嶅簲瀛楁鍙樻洿锛�/get銆�/page锛夛細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| processInstanceId | String | BPM 娴佺▼瀹炰緥缂栧彿锛屽鎵逛腑鍙婁互鍚庢湁鍊� |
+
+**鍝嶅簲绀轰緥锛坅pprove-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`锛圫elect 涓嬫媺閫夋祦绋嬶紝榛樿閫夌涓�涓紝纭鍚庤皟 submit-approval锛�
+
+### 2. 璇︽儏椤� detail/index.vue
+
+- 銆屽鏍搁�氳繃銆嶆寜閽敼涓恒�屾彁浜ゅ鎵广�嶏紝閫昏緫鍚屼笂锛屾垚鍔熷悗鍒锋柊璇︽儏
+- 鏂板銆屾煡鐪嬪鎵广�嶆寜閽細`ifShow` 涓哄鎵逛腑鐘舵�侊紝鐐瑰嚮璺� BPM 娴佺▼璇︽儏
+- 椤堕儴鎸夐挳鍖烘柊澧炲鎵逛腑鍒ゆ柇
+
+### 3. 娴佺▼杩涘害缁勪欢 workflow-timeline.vue
+
+- 姝ラ鐢� 5 姝ユ墿涓� 6 姝ワ細寰呭彂甯� 鈫� 寰呬笂浼� 鈫� 寰呭鏍� 鈫� **瀹℃壒涓�** 鈫� 寰呭綊妗� 鈫� 宸插綊妗�
+- 鐘舵�佸�煎埌姝ラ绱㈠紩鐨勬槧灏勯渶鏄惧紡澶勭悊锛堝鎵逛腑 value=5 鎻掑湪寰呭鏍镐箣鍚庯級
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鎻愪氦瀹℃壒 | 浠呭緟瀹℃牳(2)鐘舵�佸彲鎻愪氦锛涜嫢椤圭洰涓嬪瓨鍦ㄦ湭瀹℃牳鐨勮璁¤祫鏂欙紝鍚庣杩斿洖閿欒銆岃椤圭洰涓嬪瓨鍦ㄦ湭瀹℃牳鐨勮祫鏂欍�� |
+| 瀹℃壒閫氳繃 | 鐘舵�� 鈫� 寰呭綊妗�(3)锛屽彲鎵ц褰掓。 |
+| 瀹℃壒椹冲洖/鍙栨秷 | 鐘舵�� 鈫� 寰呭鏍�(2)锛屽彲淇敼鍚庨噸鏂版彁浜� |
+| 褰掓。 | 浠呭緟褰掓。(3)鐘舵�佸彲褰掓。锛屽綊妗e悗鐢熸垚鍙拌处璁板綍銆侀」鐩笉鍙啀淇敼 |
+
+## 娉ㄦ剰浜嬮」
+
+- 闇�鍏堝湪 BPM 鍚庡彴鎵嬪伐閮ㄧ讲瀹℃壒娴佺▼锛堟祦绋� key=`mes-pd-project-audit`锛屽垎绫�=`mes_pd_project_approve`锛夛紝鍚﹀垯銆屾彁浜ゅ鎵广�嶆彁绀烘殏鏃犲彲鐢ㄦ祦绋�
+- 鏈彁浜ゅ鎵癸紙processInstanceId 涓虹┖锛夋椂鐐广�屾煡鐪嬪鎵广�嶅簲鎻愮ず銆岃椤圭洰灏氭湭鎻愪氦瀹℃壒銆�
+- 鍘嗗彶鏁版嵁 status 0-4 涓嶅彈褰卞搷锛涙椂闂寸嚎缁勪欢闇�鍏煎鐘舵�佸�煎埌姝ラ鐨勯潪杩炵画鏄犲皠
diff --git a/docs/projectSql/ruoyi-vue-pro.sql b/docs/projectSql/ruoyi-vue-pro.sql
index 82f8c7a..baaafbc 100644
--- a/docs/projectSql/ruoyi-vue-pro.sql
+++ b/docs/projectSql/ruoyi-vue-pro.sql
@@ -3449,7 +3449,7 @@
   `total_tax_price` decimal(24, 6) NOT NULL COMMENT '鍚堣绋庨锛屽崟浣嶏細鍏�',
   `discount_percent` decimal(24, 6) NOT NULL COMMENT '浼樻儬鐜囷紝鐧惧垎姣�',
   `discount_price` decimal(24, 6) NOT NULL COMMENT '浼樻儬閲戦锛屽崟浣嶏細鍏�',
-  `deposit_price` decimal(24, 6) NOT NULL COMMENT '瀹氶噾閲戦锛屽崟浣嶏細鍏�',
+  `deposit_price` decimal(24, 6) NOT NULL DEFAULT 0 COMMENT '瀹氶噾閲戦锛屽崟浣嶏細鍏�',
   `file_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '闄勪欢鍦板潃',
   `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '澶囨敞',
   `process_instance_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT 'BPM 娴佺▼瀹炰緥缂栧彿',
diff --git a/docs/purchase_invoice_payment_link.md b/docs/purchase_invoice_payment_link.md
index fa6e939..388c433 100644
--- a/docs/purchase_invoice_payment_link.md
+++ b/docs/purchase_invoice_payment_link.md
@@ -2,7 +2,7 @@
 
 ## 娑夊強椤甸潰
 
-- 閲囪喘鏉ョエ绠$悊椤碉紙**鏂板缓椤甸潰**锛屽弬鑰冮噰璐敵璇�/閲囪喘璁㈠崟椤甸潰缁撴瀯锛�
+- 閲囪喘鏉ョエ绠$悊椤碉紙鏉ョエ鍒楄〃銆佹柊澧�/缂栬緫/璇︽儏寮圭獥锛�
 - 閲囪喘璁㈠崟璇︽儏椤碉紙灞曠ず璇ヨ鍗曚笅宸插叧鑱旂殑鏉ョエ淇℃伅锛�
 - 浠樻鍗曠鐞嗛〉锛堟柊寤�/缂栬緫寮圭獥銆佸垪琛ㄣ�佽鎯咃級
 - 浠樻鍗曡鎯呴〉锛堝睍绀哄叧鑱旂殑鏉ョエ淇℃伅锛�
@@ -10,15 +10,14 @@
 ## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
 
 1. 閲囪喘璁㈠崟锛堝鏍搁�氳繃锛�-> 鍒涘缓鏉ョエ锛歚purchaseOrderId` 蹇呴�夛紝渚涘簲鍟嗚嚜鍔ㄥ甫鍑猴紙浠ラ噰璐鍗曠殑渚涘簲鍟嗕负鍑嗭紝鍓嶇鏃犻渶浼� `supplierId`锛岀敱鍚庣鎸夐噰璐鍗曟煡璇㈣缃級銆�
-2. 鏉ョエ鍒涘缓鍚� -> 涓婁紶鍙戠エ闄勪欢锛堣蛋 system 妯″潡 Storage API锛夈��
-3. 鏉ョエ鎻愪氦瀹℃壒 -> 瀹℃壒閫氳繃 -> 鎵嶅彲浣滀负浠樻鍏宠仈瀵硅薄銆�
-4. 鏂板缓浠樻鍗� -> 閫夋嫨鍏宠仈鏉ョエ锛坄invoiceId` 蹇呭~锛夛細浠� `GET /erp/purchase-invoice/simple-list?supplierId=&purchaseOrderId=` 閫夋嫨锛屼粎鏄剧ず"瀹℃壒閫氳繃 + 宸蹭笂浼犻檮浠� + 鍓╀綑鍙粯閲戦 > 0"鐨勬潵绁紱閫夋嫨鍚� `remainingInvoicePrice` 鍙敤浜庨濉粯娆鹃噾棰濅笂闄愩��
+2. 鏉ョエ鍒涘缓鍚� -> 涓婁紶鍙戠エ闄勪欢锛堣蛋 system 妯″潡 Storage API锛夈��**鏉ョエ鍒涘缓鍗崇敓鏁堬紝鏃犲鏍哥幆鑺�**銆�
+3. 鏂板缓浠樻鍗� -> 閫夋嫨鍏宠仈鏉ョエ锛坄invoiceId` 蹇呭~锛夛細浠� `GET /erp/purchase-invoice/simple-list?supplierId=&purchaseOrderId=` 閫夋嫨锛屼粎鏄剧ず"宸蹭笂浼犻檮浠� + 鍓╀綑鍙粯閲戦 > 0"鐨勬潵绁紱閫夋嫨鍚� `remainingInvoicePrice` 鍙敤浜庨濉粯娆鹃噾棰濅笂闄愩��
 
 瀵瑰簲鍏崇郴锛�**閲囪喘璁㈠崟(1) -> 鏉ョエ(N) -> 浠樻(M)**銆�
 
 ## API
 
-### 1. 鏉ョエ绠$悊锛堟柊锛�
+### 1. 鏉ョエ绠$悊
 
 | 鏂规硶 | 璺緞 | 璇存槑 |
 |------|------|------|
@@ -28,9 +27,9 @@
 | GET | /erp/purchase-invoice/get?id= | 鑾峰緱鏉ョエ璇︽儏 |
 | GET | /erp/purchase-invoice/page | 鏉ョエ鍒嗛〉 |
 | GET | /erp/purchase-invoice/export-excel | 瀵煎嚭鏉ョエ Excel |
-| PUT | /erp/purchase-invoice/submit?id= | 鎻愪氦鏉ョエ瀹℃壒 |
-| GET | /erp/purchase-invoice/audit-count | 寰呭鏍告潵绁ㄦ暟閲� |
 | GET | /erp/purchase-invoice/simple-list?supplierId=&purchaseOrderId= | 绮剧畝鍒楄〃锛堜粯娆捐〃鍗曢�夋潵绁級 |
+
+> 宸茬Щ闄ゆ帴鍙o細`/erp/purchase-invoice/update-status`锛堝鏍�/鍙嶅鏍革級銆乣/erp/purchase-invoice/audit-count`锛堝緟瀹℃牳鏁伴噺锛夈�傚墠绔鍚屾鍒犻櫎瀵瑰簲鎸夐挳涓庢帴鍙h皟鐢ㄣ��
 
 **鍒涘缓/鏇存柊鏉ョエ璇锋眰鍙傛暟锛�**
 
@@ -46,17 +45,18 @@
 | remark | String | 鍚� | 澶囨敞 |
 | blobIds | List\<Long\> | 鍚� | 鍙戠エ闄勪欢 blobId 鍒楄〃 |
 
-**鏉ョエ鍒嗛〉/璇︽儏鍝嶅簲鏂板瀛楁锛�**
+**鏉ョエ鍒嗛〉/璇︽儏鍝嶅簲瀛楁锛�**
 
 | 瀛楁 | 绫诲瀷 | 璇存槑 |
 |------|------|------|
 | no | String | 鏉ョエ缂栧彿锛堝悗绔敓鎴愶紝鍓嶇紑 FPD锛� |
 | supplierName | String | 渚涘簲鍟嗗悕绉� |
 | order | Object | 閲囪喘璁㈠崟淇℃伅锛堝祵濂楋級 |
-| auditStatus | Integer | 瀹℃壒鐘舵�侊紙0鏈彁浜�/10瀹℃壒涓�/20瀹℃牳閫氳繃/30瀹℃牳涓嶉�氳繃/40宸插彇娑堬級 |
 | attachmentList | List | 鍙戠エ闄勪欢鍒楄〃 |
 | hasAttachment | Boolean | 鏄惁宸蹭笂浼犲彂绁ㄩ檮浠� |
 | remainingInvoicePrice | BigDecimal | 鍓╀綑鍙粯閲戦 = 鏉ョエ閲戦 - 璇ユ潵绁ㄥ凡浠樻閲戦 |
+
+> 宸茬Щ闄ゅ搷搴斿瓧娈碉細`auditStatus`锛堝鎵圭姸鎬侊級銆乣processInstanceId`锛堝伐浣滄祦缂栧彿锛夈��
 
 **simple-list 鍝嶅簲锛�**
 
@@ -69,18 +69,17 @@
 | price | BigDecimal | 鏉ョエ閲戦 |
 | supplierId | Long | 渚涘簲鍟嗙紪鍙� |
 | purchaseOrderId | Long | 閲囪喘璁㈠崟缂栧彿 |
-| auditStatus | Integer | 瀹℃壒鐘舵�� |
 | hasAttachment | Boolean | 鏄惁宸蹭笂浼犻檮浠讹紙鎭掍负 true锛� |
 | remainingInvoicePrice | BigDecimal | 鍓╀綑鍙粯閲戦 |
 
-### 2. 浠樻鍗曪紙鎺ュ彛鍙樻洿锛�
+### 2. 浠樻鍗�
 
 | 鏂规硶 | 璺緞 | 璇存槑 |
 |------|------|------|
-| POST | /erp/finance-payment/create | 鍒涘缓浠樻鍗曪紝璇锋眰浣撴柊澧� `invoiceId`锛堝繀濉級 |
-| PUT | /erp/finance-payment/update | 鏇存柊浠樻鍗曪紝璇锋眰浣撴柊澧� `invoiceId`锛堝繀濉級 |
-| GET | /erp/finance-payment/get | 鍝嶅簲鏂板 `invoiceId`銆乣invoice` 宓屽瀵硅薄 |
-| GET | /erp/finance-payment/page | 鍝嶅簲鏂板 `invoiceId`銆乣invoice` 宓屽瀵硅薄 |
+| POST | /erp/finance-payment/create | 鍒涘缓浠樻鍗曪紝璇锋眰浣撳惈 `invoiceId`锛堝繀濉級 |
+| PUT | /erp/finance-payment/update | 鏇存柊浠樻鍗曪紝璇锋眰浣撳惈 `invoiceId`锛堝繀濉級 |
+| GET | /erp/finance-payment/get | 鍝嶅簲鍚� `invoiceId`銆乣invoice` 宓屽瀵硅薄 |
+| GET | /erp/finance-payment/page | 鍝嶅簲鍚� `invoiceId`銆乣invoice` 宓屽瀵硅薄 |
 
 **璇锋眰鍙傛暟鏂板锛�**
 
@@ -93,7 +92,7 @@
 | 瀛楁 | 绫诲瀷 | 璇存槑 |
 |------|------|------|
 | invoiceId | Long | 鍏宠仈鐨勬潵绁ㄧ紪鍙� |
-| invoice | Object | 鍏宠仈鐨勬潵绁ㄤ俊鎭紙鍚� no銆乮nvoiceNo銆乮nvoiceTitle銆乸rice銆乤uditStatus 绛夛級 |
+| invoice | Object | 鍏宠仈鐨勬潵绁ㄤ俊鎭紙鍚� no銆乮nvoiceNo銆乮nvoiceTitle銆乸rice 绛夛級 |
 
 **鍝嶅簲绀轰緥锛�**
 
@@ -109,8 +108,7 @@
       "no": "FPD20260810000001",
       "invoiceNo": "02564178",
       "invoiceTitle": "XX绉戞妧鏈夐檺鍏徃",
-      "price": 9000.00,
-      "auditStatus": 20
+      "price": 9000.00
     },
     "paymentPrice": 5000.00
   }
@@ -127,20 +125,20 @@
 | 鍙戠エ鍙风爜/鎶ご | 鏉ョエ鍒楄〃銆佽鎯� | 褰曞叆瀛楁 |
 | 鏉ョエ閲戦 | 鏉ョエ鍒楄〃銆佽鎯呫�佷粯娆捐〃鍗� | 鍏� |
 | 鏉ョエ鏃ユ湡 | 鏉ョエ鍒楄〃銆佽鎯� | |
-| 瀹℃壒鐘舵�� | 鏉ョエ鍒楄〃銆佽鎯� | 瀛楀吀灞曠ず |
-| 鍙戠エ闄勪欢 | 鏉ョエ璇︽儏銆佹潵绁ㄥ垪琛� | 涓婁紶/棰勮/涓嬭浇 |
+| 鍙戠エ闄勪欢 | 鏉ョエ璇︽儏銆佹潵绁ㄥ垪琛� | 涓婁紶/棰勮/涓嬭浇锛屾湁闄勪欢鏄剧ず"宸蹭笂浼�"Tag |
 | 鍓╀綑鍙粯閲戦 | 鏉ョエ鍒楄〃銆佷粯娆捐〃鍗� | 鏉ョエ閲戦 - 宸蹭粯娆鹃噾棰� |
 | 鍏宠仈鏉ョエ | 浠樻鍒楄〃銆佷粯娆捐鎯� | 灞曠ず invoice.no / invoice.invoiceNo / invoice.price |
+
+> 宸茬Щ闄ゅ睍绀哄瓧娈碉細鏉ョエ鍒楄〃鐨�"瀹℃壒鐘舵��"鍒椼�佹悳绱㈡潯浠�"瀹℃牳鐘舵��"銆�"瀹℃牳/鍙嶅鏍�"鎿嶄綔鎸夐挳銆�
 
 ## 涓氬姟瑙勫垯璇存槑
 
 | 鍦烘櫙 | 瑙勫垯 |
 |------|------|
-| 鏉ョエ鍒涘缓 | 閲囪喘璁㈠崟蹇呴』瀹℃牳閫氳繃锛涙潵绁ㄤ緵搴斿晢涓庨噰璐鍗曚竴鑷达紱鏉ョエ閲戦涓嶈秴杩囪鍗曞墿浣欏彲寮�绁ㄩ噾棰� |
-| 鏉ョエ缂栬緫 | 浠�"鏈彁浜�"鐘舵�佸彲缂栬緫 |
-| 鏉ョエ鍒犻櫎 | 瀹℃壒閫氳繃鍚庝笉鍙垹闄� |
-| 鏉ョエ鎻愪氦瀹℃壒 | 浠�"鏈彁浜�"鐘舵�佸彲鎻愪氦锛屾彁浜ゅ悗杩涘叆瀹℃壒涓� |
-| 浠樻閫夋嫨鏉ョエ | 浠呭鎵归�氳繃 + 宸蹭笂浼犲彂绁ㄩ檮浠剁殑鏉ョエ鍙�� |
+| 鏉ョエ鍒涘缓 | 閲囪喘璁㈠崟蹇呴』瀹℃牳閫氳繃锛涙潵绁ㄤ緵搴斿晢涓庨噰璐鍗曚竴鑷达紱鏉ョエ閲戦涓嶈秴杩囪鍗曞墿浣欏彲寮�绁ㄩ噾棰濓紱**鍒涘缓鍗崇敓鏁堬紝鏃犻渶瀹℃牳** |
+| 鏉ョエ缂栬緫 | 浠绘剰鐘舵�佸彲缂栬緫锛屾棤瀹℃牳鐘舵�侀檺鍒� |
+| 鏉ョエ鍒犻櫎 | 宸叉湁鏈夋晥浠樻鍗曪紙鏈綔搴熶笖浠樻閲戦 > 0锛夊叧鑱旂殑鏉ョエ涓嶅彲鍒犻櫎锛屽叾浣欏彲鍒犻櫎 |
+| 浠樻閫夋嫨鏉ョエ | 宸蹭笂浼犲彂绁ㄩ檮浠朵笖鍓╀綑鍙粯閲戦 > 0 鐨勬潵绁ㄥ彲閫夛紙**涓嶅啀瑕佹眰瀹℃牳閫氳繃**锛� |
 | 浠樻閲戦涓婇檺 | 浠樻閲戦 <= 璇ユ潵绁ㄩ噾棰� - 璇ユ潵绁ㄥ凡浠樻閲戦锛堟湭瀹℃牳+瀹℃壒涓�+宸插鏍镐粯娆撅紝鎺掗櫎浣滃簾锛� |
 | 渚涘簲鍟嗕竴鑷存�� | 浠樻鐨勪緵搴斿晢蹇呴』涓庡叧鑱旀潵绁ㄧ殑渚涘簲鍟嗕竴鑷� |
 | 瀛橀噺浠樻鍗� | invoiceId 涓虹┖鐨勫瓨閲忎粯娆惧崟锛岀紪杈戞椂闇�閲嶆柊閫夋嫨鍚堟牸鏉ョエ |
@@ -151,5 +149,4 @@
 - 浠樻琛ㄥ崟閫夋嫨鏉ョエ鏃讹紝鍏堟寜渚涘簲鍟嗚繃婊わ紙`supplierId`锛夛紝鍙�夎鍗曟椂浼� `purchaseOrderId` 杩涗竴姝ヨ繃婊ゃ��
 - `remainingInvoicePrice` 涓鸿礋鏁版垨 0 鐨勬潵绁ㄤ笉浼氬嚭鐜板湪 simple-list 涓紝鍓嶇鍙洿鎺ユ寜杩斿洖鍒楄〃娓叉煋銆�
 - 浠樻閲戦鏍¢獙鍦ㄥ悗绔紝鍓嶇鍙皢鏉ョエ鐨� `remainingInvoicePrice` 浣滀负浠樻閲戦杈撳叆涓婇檺鎻愮ず銆�
-- 鏉ョエ瀹℃壒鐘舵�佸彉鍖栭�氳繃 BPM 鍥炶皟鏇存柊锛屽墠绔棤闇�澶勭悊锛屽垎椤垫帴鍙g洿鎺ヨ繑鍥炴渶鏂扮姸鎬併��
 - 鏉ョエ鍙戠エ闄勪欢涓婁紶浣跨敤 system 妯″潡 Storage API锛坲pload -> bind锛夛紝涓嶈浣跨敤鏃х増 /infra/file 鎺ュ彛銆�
diff --git a/docs/sql/erp_purchase_invoice_remove_audit.sql b/docs/sql/erp_purchase_invoice_remove_audit.sql
new file mode 100644
index 0000000..4fe4dcd
--- /dev/null
+++ b/docs/sql/erp_purchase_invoice_remove_audit.sql
@@ -0,0 +1,9 @@
+-- ERP 閲囪喘鏉ョエ鍘绘帀瀹℃牳锛氬垹闄ゅ鏍哥姸鎬佸瓧娈典笌宸ヤ綔娴佹畫鐣欏瓧娈�
+-- 鑳屾櫙锛氭潵绁ㄥ垱寤哄嵆鐢熸晥锛屾棤闇�瀹℃牳锛屼粯娆句笉鍐嶆牎楠�"鏉ョエ瀹℃牳閫氳繃"
+-- 鎵ц鏃舵満锛氭柊鍚庣閮ㄧ讲鍚庣殑鍋滄湇绐楀彛鎵ц锛堟棫浠g爜璇诲啓杩欎袱鍒楋紝鍏堝垹鍒椾細瀵艰嚧杩愯鏃舵姤閿欙級
+
+ALTER TABLE erp_purchase_invoice DROP COLUMN audit_status;
+ALTER TABLE erp_purchase_invoice DROP COLUMN process_instance_id;
+
+-- 娓呯悊瀛ゅ効鑿滃崟鏉冮檺锛堝搴斿凡鍒犻櫎鐨� /update-status 瀹℃牳鎺ュ彛锛�
+DELETE FROM system_menu WHERE permission = 'erp:purchase-invoice:update-status';
diff --git a/docs/srm_certificate_attachment_integration.md b/docs/srm_certificate_attachment_integration.md
new file mode 100644
index 0000000..3791d42
--- /dev/null
+++ b/docs/srm_certificate_attachment_integration.md
@@ -0,0 +1,113 @@
+# SRM 璧勮川闄勪欢涓婁紶 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 閲囪喘绠$悊-SRM-渚涘簲鍟嗙鐞�-璧勮川璇佷功鍒楄〃椤�
+- 閲囪喘绠$悊-SRM-渚涘簲鍟嗙鐞�-璧勮川璇佷功鏂板/缂栬緫寮圭獥
+- 閲囪喘绠$悊-SRM-鎷涙爣椤圭洰-鏂板/缂栬緫寮圭獥锛堝惈鍙戝竷涓�/鎶曟爣涓殑閮ㄥ垎鏇存柊锛�
+- 閲囪喘绠$悊-SRM-鎷涙爣椤圭洰-璇︽儏椤�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 鐢ㄦ埛鍦ㄨ〃鍗曚腑閫氳繃涓婁紶缁勪欢閫夋嫨鏂囦欢
+2. 涓婁紶缁勪欢璋冪敤 `/system/storage-blob/upload` 鑾峰緱 `blobId`
+3. 淇濆瓨涓氬姟璁板綍锛堣祫璐ㄨ瘉涔� / 鎷涙爣椤圭洰锛夋椂锛屽皢鏂囦欢 ID 鍒楄〃 `blobIds` 闅忎笟鍔℃暟鎹竴骞舵彁浜�
+4. 鍚庣鍦ㄤ笟鍔¤褰曚繚瀛樺悗锛岄�氳繃 `recordType + recordId` 灏嗘枃浠剁粦瀹氬埌 `system_storage_attachment` 涓棿琛�
+5. 鏌ヨ涓氬姟璁板綍锛堣鎯呫�佸垎椤碉級鏃讹紝鍚庣杩斿洖 `attachmentList`锛堝惈鏂囦欢 URL銆佸悕绉帮級锛屽墠绔睍绀轰笅杞介摼鎺�
+
+## API
+
+### 1. 璧勮川璇佷功闄勪欢
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /system/storage-blob/upload | 涓婁紶鏂囦欢锛屽叆鍙� multipart 鐨� `files`锛岃繑鍥� Blob 鍒楄〃锛堝惈 `id`锛� |
+| POST | /srm/supplier-certificate/create | 鍒涘缓璧勮川璇佷功锛屾柊澧炲叆鍙� `blobIds` |
+| PUT | /srm/supplier-certificate/update | 鏇存柊璧勮川璇佷功锛屾柊澧炲叆鍙� `blobIds`锛堝閲忔洿鏂伴檮浠讹級 |
+| DELETE | /srm/supplier-certificate/delete | 鍒犻櫎璧勮川璇佷功锛屽悓姝ュ垹闄ら檮浠� |
+| GET | /srm/supplier-certificate/page | 鍒嗛〉鏌ヨ锛屽搷搴旀柊澧� `attachmentList` |
+| GET | /srm/supplier-certificate/get | 璇︽儏鏌ヨ锛屽搷搴旀柊澧� `attachmentList` |
+| GET | /srm/supplier-certificate/list-by-supplier | 鎸変緵搴斿晢鏌ヨ锛屽搷搴旀柊澧� `attachmentList` |
+
+**璇锋眰鍙傛暟锛坈reate/update 鏂板瀛楁锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| blobIds | Array\<Long\> | 鍚� | 璧勮川璇佷功闄勪欢鏂囦欢 ID 鍒楄〃 |
+
+**鍝嶅簲瀛楁鏂板锛坅ttachmentList 鏁扮粍鍏冪礌锛夛細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | Long | 鏂囦欢 ID |
+| name | String | 鍘熸枃浠跺悕 |
+| url | String | 鏂囦欢璁块棶鍦板潃 |
+| previewURL | String | 棰勮鍦板潃 |
+| downloadURL | String | 涓嬭浇鍦板潃 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "id": 1,
+    "supplierId": 10,
+    "certificateName": "ISO9001璐ㄩ噺绠$悊浣撶郴",
+    "attachmentList": [
+      {
+        "id": 1001,
+        "name": "iso9001_cert.pdf",
+        "url": "https://example.com/files/xxx.pdf"
+      }
+    ]
+  }
+}
+```
+
+### 2. 鎷涙爣椤圭洰璧勮川瑕佹眰闄勪欢
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /system/storage-blob/upload | 涓婁紶鏂囦欢锛屽叆鍙� multipart 鐨� `files`锛岃繑鍥� Blob 鍒楄〃锛堝惈 `id`锛� |
+| POST | /srm/tender-project/create | 鍒涘缓鎷涙爣椤圭洰锛屾柊澧炲叆鍙� `blobIds` |
+| PUT | /srm/tender-project/update | 鏇存柊鎷涙爣椤圭洰锛屾柊澧炲叆鍙� `blobIds`锛堣崏绋�/鍙戝竷涓�/鎶曟爣涓潎澧為噺鏇存柊闄勪欢锛� |
+| PUT | /srm/tender-project/update-info | 閮ㄥ垎鏇存柊锛堝彂甯冧腑/鎶曟爣涓級锛屾柊澧炲叆鍙� `blobIds` |
+| DELETE | /srm/tender-project/delete | 鍒犻櫎鎷涙爣椤圭洰锛屽悓姝ュ垹闄ら檮浠� |
+| GET | /srm/tender-project/get | 璇︽儏鏌ヨ锛屽搷搴旀柊澧� `attachmentList` |
+| GET | /srm/tender-project/page | 鍒嗛〉鏌ヨ锛屽搷搴旀柊澧� `attachmentList` |
+
+**璇锋眰鍙傛暟锛坈reate/update/update-info 鏂板瀛楁锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| blobIds | Array\<Long\> | 鍚� | 璧勮川瑕佹眰闄勪欢鏂囦欢 ID 鍒楄〃 |
+
+**鍝嶅簲瀛楁鏂板锛�** 鍚岃祫璐ㄨ瘉涔︼紝`attachmentList` 鏁扮粍鍏冪礌鍚� `id`銆乣name`銆乣url`銆乣previewURL`銆乣downloadURL`銆�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 璇佷功闄勪欢 | 璧勮川璇佷功鍒楄〃 | 鏂板銆岄檮浠躲�嶅垪锛屾樉绀洪檮浠跺悕绉伴摼鎺ワ紝鐐瑰嚮鏂扮獥鍙f墦寮�锛涙棤闄勪欢鏄剧ず `--` |
+| 璇佷功闄勪欢 | 璧勮川璇佷功鏂板/缂栬緫寮圭獥 | 銆岃瘉涔﹂檮浠躲�嶄笂浼犻」锛屾敮鎸佸鏂囦欢锛岀紪杈戞椂鍥炴樉宸蹭笂浼犳枃浠� |
+| 璧勮川瑕佹眰闄勪欢 | 鎷涙爣椤圭洰鏂板/缂栬緫寮圭獥 | 銆岃祫璐ㄨ姹傞檮浠躲�嶄笂浼犻」锛屾敮鎸佸鏂囦欢锛岀紪杈戞椂鍥炴樉宸蹭笂浼犳枃浠� |
+| 璧勮川瑕佹眰闄勪欢 | 鎷涙爣椤圭洰璇︽儏椤� | 銆岃祫璐ㄨ姹傘�嶆姌鍙犻潰鏉垮唴灞曠ず闄勪欢鍚嶇О閾炬帴锛岀偣鍑绘柊绐楀彛鎵撳紑 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 缂栬緫鍥炴樉 | 缂栬緫鏃跺悗绔繑鍥� `attachmentList`锛屽墠绔皢鍏舵槧灏勪负涓婁紶缁勪欢鏂囦欢瀵硅薄锛坄uid`銆乣name`銆乣url`銆乣status='done'`銆乣id`锛夊苟璧嬬粰 `blobIds`锛屾彁浜ゆ椂鎸� `valueKey='id'` 鎻愬彇鏂囦欢 ID 鎻愪氦 |
+| 闄勪欢鏇存柊 | 鍚庣鎸夈�屽閲忔洿鏂般�嶅鐞嗭細浠ユ湰娆℃彁浜ょ殑 `blobIds` 涓哄噯锛岃嚜鍔ㄥ垹闄ゅ凡绉婚櫎鐨勬枃浠剁粦瀹氥�佹柊澧炴柊涓婁紶鐨勬枃浠剁粦瀹� |
+| 鍒犻櫎涓氬姟璁板綍 | 鍒犻櫎璧勮川璇佷功/鎷涙爣椤圭洰鏃讹紝鍚庣鍚屾鍒犻櫎鍏堕檮浠� |
+| 鍙戝竷涓�/鎶曟爣涓紪杈� | 鎷涙爣椤圭洰鍦ㄥ彂甯冧腑/鎶曟爣涓樁娈甸�氳繃 `update-info` 鎺ュ彛浠呰兘淇敼鎷涙爣璇存槑銆佽祫璐ㄨ姹傚強璧勮川瑕佹眰闄勪欢 |
+| 涓婁紶鏍煎紡 | 鏀寔 jpg銆乯peg銆乸ng銆乸df銆乨oc銆乨ocx锛屽崟涓枃浠朵笉瓒呰繃 2MB |
+
+## 娉ㄦ剰浜嬮」
+
+- 涓婁紶蹇呴』璧� `/system/storage-blob/upload`锛岀姝娇鐢ㄦ棫鐗� `/infra/file/*` 鎺ュ彛銆�
+- 璧勮川璇佷功涓庢嫑鏍囬」鐩娇鐢ㄤ笉鍚岀殑 `recordType`锛氳祫璐ㄨ瘉涔︿负 `srm_supplier_certificate`锛屾嫑鏍囬」鐩负 `srm_tender_project`锛屽墠绔棤闇�鍏冲績锛岀敱鍚庣澶勭悊銆�
+- 缂栬緫鍥炴樉鏃惰嫢鐢ㄦ埛鏈敼鍔ㄩ檮浠讹紝鎻愪氦鐨� `blobIds` 浠嶅簲鍖呭惈鍘熸湁鏂囦欢 ID锛堢敱涓婁紶缁勪欢鍥炴樉瀵硅薄鎼哄甫锛夛紝閬垮厤闄勪欢琚鍒犮��
+- 鎷涙爣椤圭洰閮ㄥ垎鏇存柊锛坄update-info`锛夎姹備綋鍚屾牱闇�鎼哄甫 `blobIds`銆�
+- 鍒楄〃椤甸檮浠堕摼鎺ュ缓璁� `target="_blank"` 鏂扮獥鍙f墦寮�锛屼究浜庨瑙� PDF/鍥剧墖銆�
diff --git a/docs/srm_supplier_tender_integration.md b/docs/srm_supplier_tender_integration.md
new file mode 100644
index 0000000..c8d9129
--- /dev/null
+++ b/docs/srm_supplier_tender_integration.md
@@ -0,0 +1,78 @@
+# SRM 渚涘簲鍟嗘姇鏍囬�変汉 & 涓爣鑷姩鍑嗗叆 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 鎷涙爣椤圭洰璇︽儏椤碉紙鎶曟爣绠$悊 Tab銆佽瘎鏍� Tab 鐨勪緵搴斿晢閫夋嫨锛�
+- SRM 渚涘簲鍟嗙鐞嗛〉闈紙浠呮柊澧� `getSupplierList` API锛岄〉闈㈡湰韬棤鏀瑰姩锛�
+
+## 鑳屾櫙涓庨渶姹�
+
+1. 鎷涙爣椤圭洰璇︽儏椤典腑銆屾柊澧炴姇鏍囥�嶃�岃瘎鏍囥�嶆椂閫夋嫨渚涘簲鍟嗭紝**涓嶅啀杩囨护鍑嗗叆鐘舵��**锛岀洿鎺ユ煡璇緵搴斿晢妗f鍒楄〃銆�
+2. 涓爣骞剁敓鎴愰噰璐鍗曟椂锛屽鏋滀緵搴斿晢灏氭湭鍑嗗叆锛屽悗绔細鑷姩鍒涘缓鍑嗗叆鐢宠骞剁疆涓哄凡閫氳繃锛屽墠绔棤闇�棰濆澶勭悊銆�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | /srm/supplier/list | 鑾峰彇渚涘簲鍟嗘。妗堝垪琛紙涓嶈繃婊ゅ噯鍏ョ姸鎬侊級 |
+
+**璇锋眰鍙傛暟锛�** 鏃�
+
+**鍝嶅簲锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| id | number | 渚涘簲鍟� ID |
+| name | string | 渚涘簲鍟嗗悕绉� |
+| code | string | 渚涘簲鍟嗙紪鐮� |
+| status | number | 鐘舵�侊紙0 鍚敤锛�1 绂佺敤锛� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": [
+    { "id": 1, "code": "SUP001", "name": "鏌愭煇鍏徃", "status": 0 }
+  ]
+}
+```
+
+## 鍓嶇淇敼鐐�
+
+### 1. 鏂板 API 鍑芥暟
+
+鍦� `src/api/srm/supplier/index.ts` 涓柊澧� `getSupplierList()`锛岃姹� `/srm/supplier/list`銆�
+
+> 鍘熸湁鐨� `getSupplierSimpleList()`锛堣姹� `/srm/supplier/simple-list`锛屼粎杩斿洖宸插噯鍏ヤ緵搴斿晢锛�**淇濈暀涓嶅姩**锛屽叾瀹冮〉闈紙閲囪喘璁㈠崟銆侀噰璐叆搴撱�両QC 绛夛級浠嶄娇鐢ㄥ畠銆�
+
+### 2. 鎷涙爣椤圭洰璇︽儏椤垫敼鐢ㄦ。妗堝垪琛�
+
+`src/views/srm/tender/detail.vue` 涓細
+
+- 灏� `getSupplierSimpleList` 鐨� import 鏇挎崲涓� `getSupplierList`
+- 灏� `onMounted` 涓姞杞戒緵搴斿晢鏁版嵁鐨勮皟鐢ㄦ敼涓� `getSupplierList`
+
+璇ラ〉闈㈢殑銆屾姇鏍囩鐞嗐�嶄緵搴斿晢閫夋嫨涓嬫媺銆併�岃瘎鏍囥�嶄緵搴斿晢閫夋嫨涓嬫媺锛屽潎浣跨敤鍚屼竴涓� `supplierList`锛屾浛鎹㈠悗涓や釜涓嬫媺閮戒細灞曠ず鍏ㄩ儴渚涘簲鍟嗘。妗堬紙涓嶈繃婊ゅ噯鍏ワ級銆�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| id | 涓嬫媺 value | 渚涘簲鍟� ID锛屾彁浜ょ粰鍚庣 |
+| name | 涓嬫媺 label | 渚涘簲鍟嗗悕绉� |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鎶曟爣鏃堕�夋嫨渚涘簲鍟� | 灞曠ず鍏ㄩ儴渚涘簲鍟嗘。妗堬紝涓嶅啀杩囨护鍑嗗叆鐘舵�� |
+| 璇勬爣鏃堕�夋嫨渚涘簲鍟� | 鍚屾姇鏍囷紝灞曠ず鍏ㄩ儴渚涘簲鍟嗘。妗� |
+| 涓爣鐢熸垚閲囪喘璁㈠崟 | 鑻ヤ緵搴斿晢鏈噯鍏ワ紝鍚庣鑷姩鍒涘缓鍑嗗叆鐢宠骞剁洿鎺ョ疆涓哄凡閫氳繃锛屽啀鐢熸垚閲囪喘璁㈠崟锛涘墠绔棤闇�璋冪敤鍑嗗叆鐩稿叧鎺ュ彛 |
+| 鏈噯鍏ヤ緵搴斿晢鑳藉惁鎶曟爣 | 鍙互锛屾姇鏍囦笉鍙楀噯鍏ョ姸鎬侀檺鍒� |
+
+## 娉ㄦ剰浜嬮」
+
+- `getSupplierList` 杩斿洖鐨勬暟缁勫厓绱犲惈 `id`銆乣name`锛屽彲鐩存帴鐢ㄤ簬涓嬫媺閫夐」锛坄{ id, name }`锛夈��
+- 渚涘簲鍟嗚绂佺敤锛坰tatus = 1锛夋椂鏄惁鍏佽鎶曟爣锛氭湰鎺ュ彛涓嶈繃婊ゅ噯鍏ョ姸鎬侊紝`status` 瀛楁鐢卞墠绔喅瀹氭槸鍚﹁繃婊ゅ睍绀猴紱鐩墠鎷涙爣璇︽儏椤典笅鎷変笉杩囨护銆�
+- 涓爣鑷姩鍑嗗叆鏄悗绔涓猴紝鍓嶇銆岀‘璁や腑鏍囥�嶃�岀敓鎴愰噰璐鍗曘�嶆寜閽殑璋冪敤鏂瑰紡涓嶅彉銆�
diff --git a/sql/mysql/mes_dv_metering_init.sql b/sql/mysql/mes_dv_metering_init.sql
new file mode 100644
index 0000000..0504144
--- /dev/null
+++ b/sql/mysql/mes_dv_metering_init.sql
@@ -0,0 +1,119 @@
+-- ============================================
+-- MES 璁惧绠$悊-璁¢噺鍣ㄥ叿 & 璁¢噺妫�娴嬭褰� 妯″潡鍒濆鍖栬剼鏈�
+-- 1. 寤鸿〃  2. 鑿滃崟  3. 瀛楀吀  4. 绔欏唴淇℃ā鏉�  5. 鍏ㄥ眬閰嶇疆
+-- ============================================
+
+-- 1. 寤鸿〃
+DROP TABLE IF EXISTS `mes_dv_metering_check`;
+DROP TABLE IF EXISTS `mes_dv_metering`;
+CREATE TABLE `mes_dv_metering` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+    `code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '璁¢噺鍣ㄥ叿缂栫爜',
+    `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '璁¢噺鍣ㄥ叿鍚嶇О',
+    `specification` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '瑙勬牸鍨嬪彿',
+    `brand` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '鍝佺墝/鐢熶骇鍘傚',
+    `category` int NULL DEFAULT NULL COMMENT '鍣ㄥ叿绫诲埆锛堝崱灏�/鍗冨垎灏�/鍘嬪姏琛ㄧ瓑锛夛紝瀛楀吀 mes_dv_metering_category',
+    `precision` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '绮惧害',
+    `range` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '閲忕▼',
+    `dept_id` bigint NULL DEFAULT NULL COMMENT '浣跨敤閮ㄩ棬缂栧彿',
+    `charge_user_id` bigint NULL DEFAULT NULL COMMENT '璐d换浜虹紪鍙凤紙绔欏唴淇″彂閫佸璞★級',
+    `check_cycle_count` int NOT NULL COMMENT '鏍″噯鍛ㄦ湡锛堟湀锛�',
+    `use_date` date NULL DEFAULT NULL COMMENT '鎶曞叆浣跨敤鏃ユ湡',
+    `last_check_date` date NULL DEFAULT NULL COMMENT '涓婃妫�娴嬫棩鏈�',
+    `next_check_date` date NULL DEFAULT NULL COMMENT '涓嬫妫�娴嬫棩鏈燂紙鍒版湡鏃ワ級',
+    `status` int NOT NULL COMMENT '鐘舵�侊紙鍦ㄧ敤/鍋滅敤/鎶ュ簾锛夛紝瀛楀吀 mes_dv_metering_status',
+    `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '澶囨敞',
+
+    -- 鏍囧噯瀛楁
+    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+    PRIMARY KEY (`id`) USING BTREE,
+    UNIQUE INDEX `uk_code` (`code`),
+    INDEX `idx_dept_id` (`dept_id`),
+    INDEX `idx_charge_user_id` (`charge_user_id`),
+    INDEX `idx_next_check_date` (`next_check_date`)
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MES 璁¢噺鍣ㄥ叿' ROW_FORMAT = Dynamic;
+
+CREATE TABLE `mes_dv_metering_check` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭ID',
+    `metering_id` bigint NOT NULL COMMENT '璁¢噺鍣ㄥ叿ID',
+    `check_code` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '妫�娴嬬紪鍙�',
+    `check_date` date NULL DEFAULT NULL COMMENT '妫�娴嬫棩鏈�',
+    `check_org` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '妫�娴嬫満鏋�',
+    `check_result` int NULL DEFAULT NULL COMMENT '妫�娴嬬粨璁猴紙鍚堟牸/涓嶅悎鏍硷級锛屽瓧鍏� mes_dv_metering_check_result',
+    `cert_no` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '璇佷功缂栧彿',
+    `check_user_id` bigint NULL DEFAULT NULL COMMENT '妫�娴嬩汉缂栧彿',
+    `next_check_date` date NULL DEFAULT NULL COMMENT '涓嬫妫�娴嬫棩鏈燂紙鑷姩璁$畻鎴栨墜鍔ㄦ寚瀹氾級',
+    `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '澶囨敞',
+
+    -- 鏍囧噯瀛楁
+    `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+    PRIMARY KEY (`id`) USING BTREE,
+    UNIQUE INDEX `uk_check_code` (`check_code`),
+    INDEX `idx_metering_id` (`metering_id`),
+    INDEX `idx_check_date` (`check_date`)
+) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_unicode_ci COMMENT = 'MES 璁¢噺妫�娴嬭褰�' ROW_FORMAT = Dynamic;
+
+-- 2. 鑿滃崟锛堢埗鑿滃崟 5300 璁惧绠$悊锛�
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5380, '璁¢噺鍣ㄥ叿', '', 2, 8, 5300, 'metering', 'ep:scale-to-original', 'mes/dv/metering/index', 'MesDvMetering', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(5381, '璁¢噺鍣ㄥ叿鏌ヨ', 'mes:dv-metering:query', 3, 1, 5380, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5382, '璁¢噺鍣ㄥ叿鍒涘缓', 'mes:dv-metering:create', 3, 2, 5380, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5383, '璁¢噺鍣ㄥ叿鏇存柊', 'mes:dv-metering:update', 3, 3, 5380, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5384, '璁¢噺鍣ㄥ叿鍒犻櫎', 'mes:dv-metering:delete', 3, 4, 5380, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5385, '璁¢噺鍣ㄥ叿瀵煎嚭', 'mes:dv-metering:export', 3, 5, 5380, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES (5386, '璁¢噺妫�娴嬭褰�', '', 2, 9, 5300, 'metering-check', 'ep:checked', 'mes/dv/meteringcheck/index', 'MesDvMeteringCheck', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+INSERT INTO `system_menu` (`id`, `name`, `permission`, `type`, `sort`, `parent_id`, `path`, `icon`, `component`, `component_name`, `status`, `visible`, `keep_alive`, `always_show`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+(5387, '妫�娴嬭褰曟煡璇�', 'mes:dv-metering-check:query', 3, 1, 5386, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5388, '妫�娴嬭褰曞垱寤�', 'mes:dv-metering-check:create', 3, 2, 5386, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5389, '妫�娴嬭褰曟洿鏂�', 'mes:dv-metering-check:update', 3, 3, 5386, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5390, '妫�娴嬭褰曞垹闄�', 'mes:dv-metering-check:delete', 3, 4, 5386, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(5391, '妫�娴嬭褰曞鍑�', 'mes:dv-metering-check:export', 3, 5, 5386, '', '', '', '', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0');
+
+-- 3. 瀛楀吀绫诲瀷
+INSERT INTO `system_dict_type` (`name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+('MES 璁¢噺鍣ㄥ叿绫诲埆', 'mes_dv_metering_category', 0, '璁¢噺鍣ㄥ叿绫诲埆', '1', NOW(), '1', NOW(), b'0'),
+('MES 璁¢噺鍣ㄥ叿鐘舵��', 'mes_dv_metering_status', 0, '璁¢噺鍣ㄥ叿鐘舵��', '1', NOW(), '1', NOW(), b'0'),
+('MES 璁¢噺妫�娴嬬粨璁�', 'mes_dv_metering_check_result', 0, '璁¢噺妫�娴嬬粨璁�', '1', NOW(), '1', NOW(), b'0');
+
+-- 4. 瀛楀吀鏁版嵁
+INSERT INTO `system_dict_data` (`sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `css_class`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES
+-- 鍣ㄥ叿绫诲埆
+(1, '鍗″昂', '1', 'mes_dv_metering_category', 0, '', '', '鍗″昂', '1', NOW(), '1', NOW(), b'0'),
+(2, '鍗冨垎灏�', '2', 'mes_dv_metering_category', 0, '', '', '鍗冨垎灏�', '1', NOW(), '1', NOW(), b'0'),
+(3, '鍘嬪姏琛�', '3', 'mes_dv_metering_category', 0, '', '', '鍘嬪姏琛�', '1', NOW(), '1', NOW(), b'0'),
+(4, '娓╁害璁�', '4', 'mes_dv_metering_category', 0, '', '', '娓╁害璁�', '1', NOW(), '1', NOW(), b'0'),
+(5, '涓囩敤琛�', '5', 'mes_dv_metering_category', 0, '', '', '涓囩敤琛�', '1', NOW(), '1', NOW(), b'0'),
+(6, '澶╁钩鐮濈爜', '6', 'mes_dv_metering_category', 0, '', '', '澶╁钩鐮濈爜', '1', NOW(), '1', NOW(), b'0'),
+(7, '鍏朵粬', '7', 'mes_dv_metering_category', 0, '', '', '鍏朵粬', '1', NOW(), '1', NOW(), b'0'),
+-- 鍣ㄥ叿鐘舵��
+(1, '鍦ㄧ敤', '1', 'mes_dv_metering_status', 0, 'success', '', '鍦ㄧ敤', '1', NOW(), '1', NOW(), b'0'),
+(2, '鍋滅敤', '2', 'mes_dv_metering_status', 0, 'info', '', '鍋滅敤', '1', NOW(), '1', NOW(), b'0'),
+(3, '鎶ュ簾', '3', 'mes_dv_metering_status', 0, 'danger', '', '鎶ュ簾', '1', NOW(), '1', NOW(), b'0'),
+-- 妫�娴嬬粨璁�
+(1, '鍚堟牸', '1', 'mes_dv_metering_check_result', 0, 'success', '', '鍚堟牸', '1', NOW(), '1', NOW(), b'0'),
+(2, '涓嶅悎鏍�', '2', 'mes_dv_metering_check_result', 0, 'danger', '', '涓嶅悎鏍�', '1', NOW(), '1', NOW(), b'0');
+
+-- 5. 绔欏唴淇℃ā鏉�
+INSERT INTO `system_notify_template` (`name`, `code`, `nickname`, `content`, `type`, `params`, `status`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('璁¢噺鍣ㄥ叿鍒版湡鎻愰啋', 'mes_metering_expire_remind', 'MES 绯荤粺', '璁¢噺鍣ㄥ叿銆恵meteringCode}銆憑meteringName} 灏嗕簬 {nextCheckDate} 鍒版湡锛岃鍙婃椂瀹夋帓鏍″噯/妫�瀹氥��', 2, '["meteringCode","meteringName","nextCheckDate"]', 0, '1', NOW(), '1', NOW(), b'0');
+
+-- 6. 鍏ㄥ眬閰嶇疆锛堟彁閱掑ぉ鏁帮紝榛樿 30 澶╋級
+INSERT INTO `infra_config` (`category`, `type`, `name`, `config_key`, `value`, `visible`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+VALUES ('biz', 2, 'MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋澶╂暟', 'mes.dv-metering.remind-days', '30', 0, '璁¢噺鍣ㄥ叿涓嬫妫�娴嬫棩鏈熷埌鏈熷墠 N 澶╂彁閱掞紙绔欏唴淇″彂閫佺粰璐d换浜猴級', '1', NOW(), '1', NOW(), b'0');
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java
new file mode 100644
index 0000000..5e783ea
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/BpmBusinessAttachController.java
@@ -0,0 +1,72 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachRespVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import cn.iocoder.yudao.module.bpm.service.attach.BpmBusinessAttachService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆")
+@RestController
+@RequestMapping("/bpm/business-attach")
+@Validated
+public class BpmBusinessAttachController {
+
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓涓氬姟闄勪欢閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:create')")
+    public CommonResult<Long> createBusinessAttach(@Valid @RequestBody BpmBusinessAttachSaveReqVO createReqVO) {
+        return success(businessAttachService.createBusinessAttach(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊涓氬姟闄勪欢閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:update')")
+    public CommonResult<Boolean> updateBusinessAttach(@Valid @RequestBody BpmBusinessAttachSaveReqVO updateReqVO) {
+        businessAttachService.updateBusinessAttach(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎涓氬姟闄勪欢閰嶇疆")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:delete')")
+    public CommonResult<Boolean> deleteBusinessAttach(@RequestParam("id") Long id) {
+        businessAttachService.deleteBusinessAttach(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱涓氬姟闄勪欢閰嶇疆")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:query')")
+    public CommonResult<BpmBusinessAttachRespVO> getBusinessAttach(@RequestParam("id") Long id) {
+        BpmBusinessAttachDO attach = businessAttachService.getBusinessAttach(id);
+        return success(BeanUtils.toBean(attach, BpmBusinessAttachRespVO.class));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱涓氬姟闄勪欢閰嶇疆鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('bpm:business-attach:query')")
+    public CommonResult<PageResult<BpmBusinessAttachRespVO>> getBusinessAttachPage(@Valid BpmBusinessAttachPageReqVO pageReqVO) {
+        PageResult<BpmBusinessAttachDO> pageResult = businessAttachService.getBusinessAttachPage(pageReqVO);
+        return success(BeanUtils.toBean(pageResult, BpmBusinessAttachRespVO.class));
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java
new file mode 100644
index 0000000..2790b99
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachPageReqVO.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+import org.springframework.format.annotation.DateTimeFormat;
+
+import java.time.LocalDateTime;
+
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆鍒嗛〉 Request VO")
+@Data
+public class BpmBusinessAttachPageReqVO extends PageParam {
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", example = "purchase_approve")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue")
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", example = "1")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "鍒涘缓鏃堕棿")
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
+    private LocalDateTime[] createTime;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java
new file mode 100644
index 0000000..7778dd1
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆 Response VO")
+@Data
+public class BpmBusinessAttachRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_approve")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "閲囪喘璁㈠崟瀹℃壒灞曠ず閲囪喘璁㈠崟璇︽儏")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java
new file mode 100644
index 0000000..0e659a1
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/attach/vo/attach/BpmBusinessAttachSaveReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach;
+
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.validation.InEnum;
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - BPM 涓氬姟闄勪欢閰嶇疆鏂板/淇敼 Request VO")
+@Data
+public class BpmBusinessAttachSaveReqVO {
+
+    @Schema(description = "缂栧彿", example = "1024")
+    private Long id;
+
+    @Schema(description = "娴佺▼瀹氫箟鐨� Key", requiredMode = Schema.RequiredMode.REQUIRED, example = "purchase_approve")
+    @NotEmpty(message = "娴佺▼瀹氫箟鐨� Key 涓嶈兘涓虹┖")
+    private String processDefinitionKey;
+
+    @Schema(description = "涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order", example = "erp_purchase_order")
+    private String businessType;
+
+    @Schema(description = "涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotEmpty(message = "涓氬姟璇︽儏缁勪欢璺緞涓嶈兘涓虹┖")
+    private String componentPath;
+
+    @Schema(description = "鐘舵�侊紝鍙傝 CommonStatusEnum 鏋氫妇", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    @InEnum(CommonStatusEnum.class)
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "閲囪喘璁㈠崟瀹℃壒灞曠ず閲囪喘璁㈠崟璇︽儏")
+    private String remark;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
index add6f20..7d5705b 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/controller/admin/task/BpmProcessInstanceController.java
@@ -11,6 +11,7 @@
 import cn.iocoder.yudao.module.bpm.convert.task.BpmProcessInstanceConvert;
 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.service.attach.BpmBusinessAttachService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
@@ -56,6 +57,8 @@
     private BpmProcessDefinitionService processDefinitionService;
     @Resource
     private BpmCategoryService categoryService;
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
 
     @Resource
     private AdminUserApi adminUserApi;
@@ -217,10 +220,16 @@
         List<HistoricTaskInstance> tasks = taskService.getFinishedTaskListByProcessInstanceIdWithoutCancel(processInstanceId);
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
                 convertSet(tasks, item -> Long.valueOf(item.getAssignee())));
-        return success(BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePrintData(historicProcessInstance,
+        BpmProcessPrintDataRespVO printData = BpmProcessInstanceConvert.INSTANCE.buildProcessInstancePrintData(
+                historicProcessInstance,
                 processDefinitionService.getProcessDefinitionInfo(historicProcessInstance.getProcessDefinitionId()),
                 tasks, userMap,
-                new UserSimpleBaseVO().setNickname(startUser.getNickname()).setDeptName(dept.getName())));
+                new UserSimpleBaseVO().setNickname(startUser.getNickname()).setDeptName(dept.getName()));
+        // 濉厖涓氬姟闄勪欢閰嶇疆锛屽疄鐜版墦鍗拌嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+        businessAttachService.fillBusinessForm(printData.getProcessInstance().getProcessDefinition(),
+                processDefinitionService.getProcessDefinition(historicProcessInstance.getProcessDefinitionId()).getKey(),
+                historicProcessInstance.getBusinessKey());
+        return success(printData);
     }
 
 }
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
index 2101b22..5ef8a14 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/convert/definition/BpmModelConvert.java
@@ -58,8 +58,9 @@
             List<DeptRespDTO> startDepts = metaInfo != null ? convertList(metaInfo.getStartDeptIds(), deptMap::get) : null;
             return buildModel0(model, metaInfo, form, category, deployment, processDefinition, startUsers, startDepts);
         });
-        // 鎺掑簭
-        result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort));
+        // 鎺掑簭锛氭墜宸ラ儴缃诧紙DB 鐩存彃锛夌殑妯″瀷鍙兘娌℃湁 metaInfo/sort锛屽厹搴曟帓鍒版渶鍚�
+        result.sort(Comparator.comparing(BpmModelMetaInfoVO::getSort,
+                Comparator.nullsLast(Comparator.naturalOrder())));
         return result;
     }
 
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java
new file mode 100644
index 0000000..b35ec32
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/dataobject/definition/BpmBusinessAttachDO.java
@@ -0,0 +1,55 @@
+package cn.iocoder.yudao.module.bpm.dal.dataobject.definition;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 DO
+ *
+ * 鐢ㄤ簬寤虹珛銆屾祦绋嬪畾涔� Key -> 涓氬姟璇︽儏缁勪欢銆嶇殑鏄犲皠锛屽疄鐜板鎵硅鎯呰嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("bpm_business_attach")
+@KeySequence("bpm_business_attach_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class BpmBusinessAttachDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 娴佺▼瀹氫箟鐨� Key
+     */
+    private String processDefinitionKey;
+    /**
+     * 涓氬姟绫诲瀷锛屼緥濡� erp_purchase_order
+     */
+    private String businessType;
+    /**
+     * 涓氬姟璇︽儏缁勪欢璺緞锛屼緥濡� /erp/purchase/order/detail/index.vue
+     */
+    private String componentPath;
+    /**
+     * 鐘舵��
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.framework.common.enums.CommonStatusEnum}
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java
new file mode 100644
index 0000000..05cc9fe
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/dal/mysql/attach/BpmBusinessAttachMapper.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.bpm.dal.mysql.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface BpmBusinessAttachMapper extends BaseMapperX<BpmBusinessAttachDO> {
+
+    default PageResult<BpmBusinessAttachDO> selectPage(BpmBusinessAttachPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<BpmBusinessAttachDO>()
+                .likeIfPresent(BpmBusinessAttachDO::getProcessDefinitionKey, reqVO.getProcessDefinitionKey())
+                .likeIfPresent(BpmBusinessAttachDO::getBusinessType, reqVO.getBusinessType())
+                .likeIfPresent(BpmBusinessAttachDO::getComponentPath, reqVO.getComponentPath())
+                .eqIfPresent(BpmBusinessAttachDO::getStatus, reqVO.getStatus())
+                .betweenIfPresent(BpmBusinessAttachDO::getCreateTime, reqVO.getCreateTime())
+                .orderByDesc(BpmBusinessAttachDO::getId));
+    }
+
+    default BpmBusinessAttachDO selectByProcessDefinitionKey(String processDefinitionKey) {
+        return selectOne(BpmBusinessAttachDO::getProcessDefinitionKey, processDefinitionKey);
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
index 0537fe3..a598ff5 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/enums/ErrorCodeConstants.java
@@ -89,4 +89,8 @@
     // ========== BPM 娴佺▼琛ㄨ揪寮� 1-009-014-000 ==========
     ErrorCode PROCESS_EXPRESSION_NOT_EXISTS = new ErrorCode(1_009_014_000, "娴佺▼琛ㄨ揪寮忎笉瀛樺湪");
 
+    // ========== BPM 涓氬姟闄勪欢閰嶇疆 1-009-015-000 ==========
+    ErrorCode BUSINESS_ATTACH_NOT_EXISTS = new ErrorCode(1_009_015_000, "涓氬姟闄勪欢閰嶇疆涓嶅瓨鍦�");
+    ErrorCode BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE = new ErrorCode(1_009_015_001, "娴佺▼瀹氫箟鐨� Key({})宸插瓨鍦�");
+
 }
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java
new file mode 100644
index 0000000..734d37f
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachService.java
@@ -0,0 +1,75 @@
+package cn.iocoder.yudao.module.bpm.service.attach;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import jakarta.validation.Valid;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface BpmBusinessAttachService {
+
+    /**
+     * 鍒涘缓涓氬姟闄勪欢閰嶇疆
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createBusinessAttach(@Valid BpmBusinessAttachSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊涓氬姟闄勪欢閰嶇疆
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateBusinessAttach(@Valid BpmBusinessAttachSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎涓氬姟闄勪欢閰嶇疆
+     *
+     * @param id 缂栧彿
+     */
+    void deleteBusinessAttach(Long id);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆
+     *
+     * @param id 缂栧彿
+     * @return 涓氬姟闄勪欢閰嶇疆
+     */
+    BpmBusinessAttachDO getBusinessAttach(Long id);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 涓氬姟闄勪欢閰嶇疆鍒嗛〉
+     */
+    PageResult<BpmBusinessAttachDO> getBusinessAttachPage(BpmBusinessAttachPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱涓氬姟闄勪欢閰嶇疆锛屽熀浜庢祦绋嬪畾涔� Key
+     *
+     * @param processDefinitionKey 娴佺▼瀹氫箟 Key
+     * @return 涓氬姟闄勪欢閰嶇疆
+     */
+    BpmBusinessAttachDO getBusinessAttachByProcessDefinitionKey(String processDefinitionKey);
+
+    /**
+     * 濉厖涓氬姟琛ㄥ崟淇℃伅鍒版祦绋嬪畾涔� Response
+     *
+     * 鑻ヨ娴佺▼瀹氫箟閰嶇疆浜嗕笟鍔¢檮浠讹紙涓斿凡鍚敤銆佸綋鍓嶆槸 NORMAL 娴佺▼琛ㄥ崟銆佸瓨鍦ㄤ笟鍔′富閿級锛�
+     * 鍒欏皢 formType 瑕嗙洊涓� CUSTOM 骞舵敞鍏ヤ笟鍔¤鎯呯粍浠惰矾寰勶紝瀹炵幇瀹℃壒璇︽儏鑷姩灞曠ず涓氬姟淇℃伅
+     *
+     * @param definitionResp        娴佺▼瀹氫箟 Response
+     * @param processDefinitionKey  娴佺▼瀹氫箟 Key
+     * @param businessKey           涓氬姟鐨勫敮涓�鏍囪瘑锛堟湭鍙戣捣鏃朵负 null锛�
+     */
+    void fillBusinessForm(BpmProcessDefinitionRespVO definitionResp, String processDefinitionKey, String businessKey);
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java
new file mode 100644
index 0000000..2d14ef5
--- /dev/null
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/attach/BpmBusinessAttachServiceImpl.java
@@ -0,0 +1,119 @@
+package cn.iocoder.yudao.module.bpm.service.attach;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachPageReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.attach.vo.attach.BpmBusinessAttachSaveReqVO;
+import cn.iocoder.yudao.module.bpm.controller.admin.definition.vo.process.BpmProcessDefinitionRespVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmBusinessAttachDO;
+import cn.iocoder.yudao.module.bpm.dal.mysql.attach.BpmBusinessAttachMapper;
+import cn.iocoder.yudao.module.bpm.enums.definition.BpmModelFormTypeEnum;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.util.Objects;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.BUSINESS_ATTACH_NOT_EXISTS;
+import static cn.iocoder.yudao.module.bpm.enums.ErrorCodeConstants.BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE;
+
+/**
+ * BPM 涓氬姟闄勪欢閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class BpmBusinessAttachServiceImpl implements BpmBusinessAttachService {
+
+    @Resource
+    private BpmBusinessAttachMapper businessAttachMapper;
+
+    @Override
+    public Long createBusinessAttach(BpmBusinessAttachSaveReqVO createReqVO) {
+        // 鏍¢獙娴佺▼瀹氫箟 Key 鍞竴
+        validateProcessDefinitionKeyUnique(createReqVO);
+        // 鎻掑叆
+        BpmBusinessAttachDO attach = BeanUtils.toBean(createReqVO, BpmBusinessAttachDO.class);
+        businessAttachMapper.insert(attach);
+        return attach.getId();
+    }
+
+    @Override
+    public void updateBusinessAttach(BpmBusinessAttachSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateBusinessAttachExists(updateReqVO.getId());
+        // 鏍¢獙娴佺▼瀹氫箟 Key 鍞竴
+        validateProcessDefinitionKeyUnique(updateReqVO);
+        // 鏇存柊
+        BpmBusinessAttachDO updateObj = BeanUtils.toBean(updateReqVO, BpmBusinessAttachDO.class);
+        businessAttachMapper.updateById(updateObj);
+    }
+
+    private void validateProcessDefinitionKeyUnique(BpmBusinessAttachSaveReqVO reqVO) {
+        BpmBusinessAttachDO attach = businessAttachMapper.selectByProcessDefinitionKey(reqVO.getProcessDefinitionKey());
+        if (attach == null || ObjUtil.equal(attach.getId(), reqVO.getId())) {
+            return;
+        }
+        throw exception(BUSINESS_ATTACH_PROCESS_DEFINITION_KEY_DUPLICATE, reqVO.getProcessDefinitionKey());
+    }
+
+    @Override
+    public void deleteBusinessAttach(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateBusinessAttachExists(id);
+        // 鍒犻櫎
+        businessAttachMapper.deleteById(id);
+    }
+
+    private BpmBusinessAttachDO validateBusinessAttachExists(Long id) {
+        BpmBusinessAttachDO attach = businessAttachMapper.selectById(id);
+        if (attach == null) {
+            throw exception(BUSINESS_ATTACH_NOT_EXISTS);
+        }
+        return attach;
+    }
+
+    @Override
+    public BpmBusinessAttachDO getBusinessAttach(Long id) {
+        return businessAttachMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<BpmBusinessAttachDO> getBusinessAttachPage(BpmBusinessAttachPageReqVO pageReqVO) {
+        return businessAttachMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public BpmBusinessAttachDO getBusinessAttachByProcessDefinitionKey(String processDefinitionKey) {
+        return businessAttachMapper.selectByProcessDefinitionKey(processDefinitionKey);
+    }
+
+    @Override
+    public void fillBusinessForm(BpmProcessDefinitionRespVO definitionResp, String processDefinitionKey, String businessKey) {
+        // 鏈彂璧� / 鏃犱笟鍔′富閿細涓嶆敞鍏�
+        if (definitionResp == null || StrUtil.isBlank(businessKey)) {
+            return;
+        }
+        // 闈� NORMAL 娴佺▼琛ㄥ崟锛堝凡鎵嬪姩閰嶇疆 CUSTOM锛夛細涓嶈鐩�
+        if (!Objects.equals(definitionResp.getFormType(), BpmModelFormTypeEnum.NORMAL.getType())) {
+            return;
+        }
+        // 鏈懡涓厤缃� / 閰嶇疆琚鐢細淇濇寔鍘熸牱
+        BpmBusinessAttachDO attach = getBusinessAttachByProcessDefinitionKey(processDefinitionKey);
+        if (attach == null || !Objects.equals(attach.getStatus(), CommonStatusEnum.ENABLE.getStatus())) {
+            return;
+        }
+        // 瑕嗙洊涓� CUSTOM锛屾敞鍏ヤ笟鍔¤鎯呯粍浠惰矾寰�
+        definitionResp.setFormType(BpmModelFormTypeEnum.CUSTOM.getType());
+        definitionResp.setFormCustomViewPath(attach.getComponentPath());
+        // 娓呯┖鏅�氳〃鍗曞瓧娈碉細璇︽儏椤� CUSTOM 鍒嗘敮涓嶈锛涙墦鍗伴〉鎸� formFields.length == 0 鍒ゆ柇娓叉煋涓氬姟缁勪欢
+        definitionResp.setFormFields(null);
+        definitionResp.setFormConf(null);
+    }
+
+}
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
index f7d2dd5..85ff097 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/task/BpmProcessInstanceServiceImpl.java
@@ -37,6 +37,7 @@
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.BpmnModelUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.FlowableUtils;
 import cn.iocoder.yudao.module.bpm.framework.flowable.core.util.SimpleModelUtils;
+import cn.iocoder.yudao.module.bpm.service.attach.BpmBusinessAttachService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.bpm.service.message.BpmMessageService;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
@@ -123,6 +124,9 @@
 
     @Resource
     private BpmProcessIdRedisDAO processIdRedisDAO;
+
+    @Resource
+    private BpmBusinessAttachService businessAttachService;
 
     // ========== Query 鏌ヨ鐩稿叧鏂规硶 ==========
 
@@ -389,9 +393,14 @@
         Map<String, String> formFieldsPermission = getFormFieldsPermission(bpmnModel, reqVO.getActivityId(), taskId);
 
         // 3. 鎷兼帴鏁版嵁
-        return BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel, processDefinition,
-                processDefinitionInfo, processInstance,
+        BpmApprovalDetailRespVO detailRespVO = BpmProcessInstanceConvert.INSTANCE.buildApprovalDetail(bpmnModel,
+                processDefinition, processDefinitionInfo, processInstance,
                 processInstanceStatus, approveNodes, todoTask, formFieldsPermission, userMap, deptMap);
+
+        // 4. 濉厖涓氬姟闄勪欢閰嶇疆锛屽疄鐜板鎵硅鎯呰嚜鍔ㄥ睍绀哄閮ㄤ笟鍔′俊鎭�
+        businessAttachService.fillBusinessForm(detailRespVO.getProcessDefinition(), processDefinition.getKey(),
+                processInstance != null ? processInstance.getBusinessKey() : null);
+        return detailRespVO;
     }
 
     /**
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
index 7e8f217..0c51c0e 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/CrmBusinessController.java
@@ -36,9 +36,7 @@
 import org.springframework.web.bind.annotation.*;
 
 import java.io.IOException;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
+import java.util.*;
 import java.util.stream.Stream;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -123,18 +121,39 @@
         List<CrmBusinessProductDO> businessProducts = businessService.getBusinessProductListByBusinessId(businessVO.getId());
         Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
                 convertSet(businessProducts, CrmBusinessProductDO::getItemId));
-        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(
-                convertSet(businessProducts, CrmBusinessProductDO::getItemUnitId));
+        // 鏀堕泦鎵�鏈夐渶瑕佹煡璇㈢殑鍗曚綅ID锛堜富鍗曚綅 + 杈呭崟浣�1 + 杈呭崟浣�2锛�
+        Set<Long> unitIds = new HashSet<>();
+        unitIds.addAll(convertSet(businessProducts, CrmBusinessProductDO::getItemUnitId));
+        itemMap.values().forEach(item -> {
+            if (item.getUnitMeasureId2() != null) {
+                unitIds.add(item.getUnitMeasureId2());
+            }
+            if (item.getUnitMeasureId3() != null) {
+                unitIds.add(item.getUnitMeasureId3());
+            }
+        });
+        Map<Long, MdmUnitMeasureRespDTO> unitMap = mdmUnitMeasureApi.getUnitMeasureMap(unitIds);
         businessVO.setProducts(BeanUtils.toBean(businessProducts, CrmBusinessRespVO.Product.class, businessProductVO ->
                 MapUtils.findAndThen(itemMap, businessProductVO.getItemId(),
                         item -> businessProductVO.setItemName(item.getName())
                                 .setItemCode(item.getCode())
                                 .setBarCode(item.getBarCode())
                                 .setItemSpecification(item.getSpecification()))));
-        // 濉厖鍗曚綅鍚嶇О
+        // 濉厖鍗曚綅鍚嶇О锛堜富鍗曚綅 + 杈呭崟浣�1 + 杈呭崟浣�2锛�
         businessVO.getProducts().forEach(product -> {
             MapUtils.findAndThen(unitMap, product.getItemUnitId(),
                     unit -> product.setItemUnitName(unit.getName()));
+            // 濉厖杈呭崟浣嶅悕绉�
+            MapUtils.findAndThen(itemMap, product.getItemId(), item -> {
+                if (item.getUnitMeasureId2() != null) {
+                    MapUtils.findAndThen(unitMap, item.getUnitMeasureId2(),
+                            unit -> product.setItemUnitName2(unit.getName()));
+                }
+                if (item.getUnitMeasureId3() != null) {
+                    MapUtils.findAndThen(unitMap, item.getUnitMeasureId3(),
+                            unit -> product.setItemUnitName3(unit.getName()));
+                }
+            });
         });
         return businessVO;
     }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
index f6cd8db..b910cf0 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessRespVO.java
@@ -134,6 +134,10 @@
         private Long itemUnitId;
         @Schema(description = "璁¢噺鍗曚綅鍚嶇О", example = "涓�")
         private String itemUnitName;
+        @Schema(description = "杈呭崟浣�1鍚嶇О")
+        private String itemUnitName2;
+        @Schema(description = "杈呭崟浣�2鍚嶇О")
+        private String itemUnitName3;
 
         @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
         private String barCode;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
index 4550df6..edb91c0 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java
@@ -44,5 +44,25 @@
     private List<Long> contactIds;
 
     @Schema(description = "鍥剧墖 URL 鍒楄〃")
-    private List<String> picUrls;
+    private Object picUrls; // 鎺ュ彈瀛楃涓叉垨鏁扮粍
+
+    /**
+     * 鑾峰彇鍥剧墖 URL 鍒楄〃
+     */
+    public List<String> getPicUrlList() {
+        if (picUrls == null) {
+            return null;
+        }
+        if (picUrls instanceof List) {
+            return (List<String>) picUrls;
+        }
+        if (picUrls instanceof String) {
+            String str = (String) picUrls;
+            if (str.isEmpty()) {
+                return null;
+            }
+            return java.util.Arrays.asList(str.split(","));
+        }
+        return null;
+    }
 }
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
index 58b5dba..fda9a92 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java
@@ -161,9 +161,14 @@
         // 1. 鏋勫缓鐗╂枡鏄庣粏
         if (CollUtil.isNotEmpty(productList)) {
             Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(productList, CrmSaleQuotationProductDO::getItemId));
+            java.util.Set<Long> unitIds = new java.util.HashSet<>(convertSet(productList, CrmSaleQuotationProductDO::getItemUnitId));
+            itemMap.values().forEach(item -> {
+                if (item.getUnitMeasureId2() != null) {
+                    unitIds.add(item.getUnitMeasureId2());
+                }
+            });
             Map<Long, MdmUnitMeasureRespDTO> unitMap = convertMap(
-                    mdmUnitMeasureApi.getUnitMeasureList(
-                            convertSet(productList, CrmSaleQuotationProductDO::getItemUnitId)).getCheckedData(),
+                    mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData(),
                     MdmUnitMeasureRespDTO::getId);
             respVO.setItems(BeanUtils.toBean(productList, CrmSaleQuotationRespVO.Item.class, item -> {
                 MdmItemRespDTO mdmItem = itemMap.get(item.getItemId());
@@ -171,8 +176,13 @@
                     item.setItemCode(mdmItem.getCode());
                     item.setItemName(mdmItem.getName());
                     item.setItemBarCode(mdmItem.getBarCode());
+                    item.setItemSpecification(mdmItem.getSpecification());
+                    item.setItemUnitId2(mdmItem.getUnitMeasureId2());
+                    item.setItemUnitId3(mdmItem.getUnitMeasureId3());
                 }
                 MapUtils.findAndThen(unitMap, item.getItemUnitId(), unit -> item.setItemUnitName(unit.getName()));
+                MapUtils.findAndThen(unitMap, item.getItemUnitId2(), unit -> item.setItemUnitName2(unit.getName()));
+                MapUtils.findAndThen(unitMap, item.getItemUnitId3(), unit -> item.setItemUnitName3(unit.getName()));
             }));
         }
         // 2. 濉厖瀹㈡埛鍚嶇О
@@ -206,6 +216,15 @@
         Map<Long, List<CrmSaleQuotationProductDO>> productMap = convertMultiMap(productList, CrmSaleQuotationProductDO::getQuotationId);
         // 1.2 鐗╂枡淇℃伅
         Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(convertSet(productList, CrmSaleQuotationProductDO::getItemId));
+        java.util.Set<Long> unitIds = new java.util.HashSet<>(convertSet(productList, CrmSaleQuotationProductDO::getItemUnitId));
+        itemMap.values().forEach(item -> {
+            if (item.getUnitMeasureId2() != null) {
+                unitIds.add(item.getUnitMeasureId2());
+            }
+        });
+        Map<Long, MdmUnitMeasureRespDTO> unitMap = convertMap(
+                mdmUnitMeasureApi.getUnitMeasureList(unitIds).getCheckedData(),
+                MdmUnitMeasureRespDTO::getId);
         // 1.3 瀹㈡埛淇℃伅
         Map<Long, CrmCustomerDO> customerMap = customerService.getCustomerMap(
                 convertSet(pageResult.getList(), CrmSaleQuotationDO::getCustomerId));
@@ -222,8 +241,18 @@
         // 2. 寮�濮嬫嫾鎺�
         return BeanUtils.toBean(pageResult, CrmSaleQuotationRespVO.class, vo -> {
             vo.setItems(BeanUtils.toBean(productMap.get(vo.getId()), CrmSaleQuotationRespVO.Item.class,
-                    item -> MapUtils.findAndThen(itemMap, item.getItemId(), mdmItem -> item.setItemCode(mdmItem.getCode())
-                            .setItemName(mdmItem.getName()).setItemBarCode(mdmItem.getBarCode()))));
+                    item -> {
+                        MapUtils.findAndThen(itemMap, item.getItemId(), mdmItem -> item
+                                .setItemCode(mdmItem.getCode())
+                                .setItemName(mdmItem.getName())
+                                .setItemBarCode(mdmItem.getBarCode())
+                                .setItemSpecification(mdmItem.getSpecification())
+                                .setItemUnitId2(mdmItem.getUnitMeasureId2())
+                                .setItemUnitId3(mdmItem.getUnitMeasureId3()));
+                        MapUtils.findAndThen(unitMap, item.getItemUnitId(), unit -> item.setItemUnitName(unit.getName()));
+                        MapUtils.findAndThen(unitMap, item.getItemUnitId2(), unit -> item.setItemUnitName2(unit.getName()));
+                        MapUtils.findAndThen(unitMap, item.getItemUnitId3(), unit -> item.setItemUnitName3(unit.getName()));
+                    }));
             MapUtils.findAndThen(customerMap, vo.getCustomerId(), customer -> vo.setCustomerName(customer.getName()));
             MapUtils.findAndThen(userMap, vo.getOwnerUserId(), user -> vo.setOwnerUserName(user.getNickname()));
             MapUtils.findAndThen(deptMap, vo.getOwnerUserId(), dept -> vo.setOwnerUserDeptName(dept.getName()));
@@ -258,4 +287,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
index ad95d47..1dc2e15 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java
@@ -141,10 +141,20 @@
         private String itemName;
         @Schema(description = "鐗╂枡鏉$爜", example = "BAR001")
         private String itemBarCode;
+        @Schema(description = "瑙勬牸鍨嬪彿", example = "M8*20")
+        private String itemSpecification;
         @Schema(description = "璁¢噺鍗曚綅缂栧彿", example = "1")
         private Long itemUnitId;
         @Schema(description = "璁¢噺鍗曚綅鍚嶇О", example = "涓�")
         private String itemUnitName;
+        @Schema(description = "杈呭崟浣�1缂栧彿", example = "2")
+        private Long itemUnitId2;
+        @Schema(description = "杈呭崟浣�1鍚嶇О", example = "绠�")
+        private String itemUnitName2;
+        @Schema(description = "杈呭崟浣�2缂栧彿", example = "3")
+        private Long itemUnitId3;
+        @Schema(description = "杈呭崟浣�2鍚嶇О", example = "鎵�")
+        private String itemUnitName3;
 
         @Schema(description = "鐗╂枡鍘熶环", requiredMode = Schema.RequiredMode.REQUIRED, example = "100.00")
         private BigDecimal itemPrice;
@@ -169,4 +179,4 @@
 
     }
 
-}
\ No newline at end of file
+}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
index 379bd62..110a803 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java
@@ -67,8 +67,10 @@
     @Override
     @CrmPermission(bizTypeValue = "#createReqVO.bizType", bizId = "#createReqVO.bizId", level = CrmPermissionLevelEnum.WRITE)
     public Long createFollowUpRecord(CrmFollowUpRecordSaveReqVO createReqVO) {
-        // 1. 鍒涘缓鏇磋繘璁板綍
+        // 1. 鍒涘缓璺熻繘璁板綍
         CrmFollowUpRecordDO record = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class);
+        // 鎵嬪姩澶勭悊 picUrls 瀛楁
+        record.setPicUrls(createReqVO.getPicUrlList());
         crmFollowUpRecordMapper.insert(record);
         storageAttachmentApi.bindAttachments("file", "crm_follow_up_record",
                 record.getId(), createReqVO.getBlobIds());
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
index 8d00f34..28c882a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseInvoiceController.java
@@ -15,7 +15,6 @@
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderRespVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
-import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpFinancePaymentService;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceService;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceServiceImpl;
@@ -169,24 +168,8 @@
         });
     }
 
-    @PutMapping("/update-status")
-    @Operation(summary = "鏇存柊鏉ョエ鐨勭姸鎬侊紙瀹℃牳/鍙嶅鏍革級")
-    @PreAuthorize("@ss.hasPermission('erp:purchase-invoice:update-status')")
-    public CommonResult<Boolean> updatePurchaseInvoiceStatus(@RequestParam("id") Long id,
-                                                             @RequestParam("status") Integer status) {
-        invoiceService.updatePurchaseInvoiceStatus(id, status);
-        return success(true);
-    }
-
-    @GetMapping("/audit-count")
-    @Operation(summary = "鑾峰緱寰呭鏍告潵绁ㄦ暟閲�")
-    @PreAuthorize("@ss.hasPermission('erp:purchase-invoice:query')")
-    public CommonResult<Long> getAuditInvoiceCount() {
-        return success(invoiceService.getAuditInvoiceCount());
-    }
-
     @GetMapping("/simple-list")
-    @Operation(summary = "鑾峰緱鏉ョエ绮剧畝鍒楄〃", description = "鍙寘鍚鏍搁�氳繃涓斿凡涓婁紶鍙戠エ闄勪欢鐨勬潵绁紝涓昏鐢ㄤ簬浠樻鍒涘缓鏃堕�夋嫨鍏宠仈鏉ョエ")
+    @Operation(summary = "鑾峰緱鏉ョエ绮剧畝鍒楄〃", description = "鍙寘鍚凡涓婁紶鍙戠エ闄勪欢鐨勬潵绁紝涓昏鐢ㄤ簬浠樻鍒涘缓鏃堕�夋嫨鍏宠仈鏉ョエ")
     @Parameter(name = "supplierId", description = "渚涘簲鍟嗙紪鍙�", required = true)
     @Parameter(name = "purchaseOrderId", description = "閲囪喘璁㈠崟缂栧彿锛堝彲閫夛紝鐢ㄤ簬鎸夎鍗曡繃婊わ級")
     @PreAuthorize("@ss.hasPermission('erp:purchase-invoice:query')")
@@ -195,11 +178,9 @@
             @RequestParam(value = "purchaseOrderId", required = false) Long purchaseOrderId) {
         List<ErpPurchaseInvoiceDO> invoices;
         if (purchaseOrderId != null) {
-            invoices = invoiceService.getPurchaseInvoiceListByOrderIdAndStatus(purchaseOrderId,
-                    List.of(ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus()));
+            invoices = invoiceService.getPurchaseInvoiceListByOrderId(purchaseOrderId);
         } else {
-            invoices = invoiceService.getPurchaseInvoiceListBySupplierIdAndStatus(supplierId,
-                    List.of(ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus()));
+            invoices = invoiceService.getPurchaseInvoiceListBySupplierId(supplierId);
         }
         if (CollUtil.isEmpty(invoices)) {
             return success(Collections.emptyList());
@@ -225,7 +206,7 @@
                     .setId(invoice.getId()).setNo(invoice.getNo()).setInvoiceNo(invoice.getInvoiceNo())
                     .setInvoiceTitle(invoice.getInvoiceTitle()).setPrice(invoice.getPrice())
                     .setSupplierId(invoice.getSupplierId()).setPurchaseOrderId(invoice.getPurchaseOrderId())
-                    .setAuditStatus(invoice.getAuditStatus()).setHasAttachment(true)
+                    .setHasAttachment(true)
                     .setRemainingInvoicePrice(remainingPrice));
         }
         return success(result);
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoicePageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoicePageReqVO.java
index e92270c..172c373 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoicePageReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoicePageReqVO.java
@@ -1,8 +1,6 @@
 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice;
 
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
-import cn.iocoder.yudao.framework.common.validation.InEnum;
-import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.EqualsAndHashCode;
@@ -22,9 +20,5 @@
 
     @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "17386")
     private Long purchaseOrderId;
-
-    @Schema(description = "瀹℃壒鐘舵��", example = "20")
-    @InEnum(ErpPurchaseInvoiceAuditStatusEnum.class)
-    private Integer auditStatus;
 
 }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java
index 3d84083..e2cd5db 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/invoice/ErpPurchaseInvoiceRespVO.java
@@ -1,9 +1,6 @@
 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice;
 
-import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
-import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderRespVO;
-import cn.iocoder.yudao.module.erp.enums.DictTypeConstants;
 import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
@@ -54,15 +51,6 @@
     @Schema(description = "鏉ョエ鏃ユ湡", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024-02-02")
     @ExcelProperty("鏉ョエ鏃ユ湡")
     private LocalDateTime invoiceTime;
-
-    @Schema(description = "瀹℃壒鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
-    @ExcelProperty(value = "瀹℃壒鐘舵��", converter = DictConvert.class)
-    @DictFormat(DictTypeConstants.AUDIT_STATUS)
-    private Integer auditStatus;
-
-    @Schema(description = "宸ヤ綔娴佺紪鍙�", example = "1043")
-    @ExcelProperty("宸ヤ綔娴佺紪鍙�")
-    private String processInstanceId;
 
     @Schema(description = "澶囨敞", example = "澶囨敞")
     @ExcelProperty("澶囨敞")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceDO.java
index f86231c..53e1781 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseInvoiceDO.java
@@ -1,7 +1,6 @@
 package cn.iocoder.yudao.module.erp.dal.dataobject.purchase;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
-import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum;
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
@@ -66,17 +65,5 @@
      * 澶囨敞
      */
     private String remark;
-    /**
-     * 宸ヤ綔娴佺紪鍙�
-     *
-     * 鍏宠仈 ProcessInstance 鐨� id 灞炴��
-     */
-    private String processInstanceId;
-    /**
-     * 瀹℃壒鐘舵��
-     *
-     * 鏋氫妇 {@link ErpPurchaseInvoiceAuditStatusEnum}
-     */
-    private Integer auditStatus;
 
 }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java
index cdbef63..e10010c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseInvoiceMapper.java
@@ -5,11 +5,8 @@
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoicePageReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
-import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum;
-import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import org.apache.ibatis.annotations.Mapper;
 
-import java.util.Collection;
 import java.util.List;
 
 /**
@@ -24,34 +21,22 @@
         return selectOne(ErpPurchaseInvoiceDO::getNo, no);
     }
 
-    default int updateByIdAndStatus(Long id, Integer status, ErpPurchaseInvoiceDO updateObj) {
-        return update(updateObj, new LambdaUpdateWrapper<ErpPurchaseInvoiceDO>()
-                .eq(ErpPurchaseInvoiceDO::getId, id).eq(ErpPurchaseInvoiceDO::getAuditStatus, status));
-    }
-
     default PageResult<ErpPurchaseInvoiceDO> selectPage(ErpPurchaseInvoicePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>()
                 .likeIfPresent(ErpPurchaseInvoiceDO::getNo, reqVO.getNo())
                 .eqIfPresent(ErpPurchaseInvoiceDO::getSupplierId, reqVO.getSupplierId())
                 .eqIfPresent(ErpPurchaseInvoiceDO::getPurchaseOrderId, reqVO.getPurchaseOrderId())
-                .eqIfPresent(ErpPurchaseInvoiceDO::getAuditStatus, reqVO.getAuditStatus())
                 .orderByDesc(ErpPurchaseInvoiceDO::getId));
     }
 
-    default List<ErpPurchaseInvoiceDO> selectListByOrderIdAndStatus(Long orderId, Collection<Integer> auditStatuses) {
+    default List<ErpPurchaseInvoiceDO> selectListByOrderId(Long orderId) {
         return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>()
-                .eq(ErpPurchaseInvoiceDO::getPurchaseOrderId, orderId)
-                .in(ErpPurchaseInvoiceDO::getAuditStatus, auditStatuses));
+                .eq(ErpPurchaseInvoiceDO::getPurchaseOrderId, orderId));
     }
 
-    default List<ErpPurchaseInvoiceDO> selectListBySupplierIdAndStatus(Long supplierId, Collection<Integer> auditStatuses) {
+    default List<ErpPurchaseInvoiceDO> selectListBySupplierId(Long supplierId) {
         return selectList(new LambdaQueryWrapperX<ErpPurchaseInvoiceDO>()
-                .eq(ErpPurchaseInvoiceDO::getSupplierId, supplierId)
-                .in(ErpPurchaseInvoiceDO::getAuditStatus, auditStatuses));
-    }
-
-    default Long selectCountByAudit() {
-        return selectCount(ErpPurchaseInvoiceDO::getAuditStatus, ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus());
+                .eq(ErpPurchaseInvoiceDO::getSupplierId, supplierId));
     }
 
 }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseInvoiceAuditStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseInvoiceAuditStatusEnum.java
deleted file mode 100644
index 3363972..0000000
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseInvoiceAuditStatusEnum.java
+++ /dev/null
@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.erp.enums;
-
-import cn.iocoder.yudao.framework.common.core.ArrayValuable;
-import lombok.Getter;
-import lombok.RequiredArgsConstructor;
-
-import java.util.Arrays;
-
-/**
- * ERP 閲囪喘鏉ョエ瀹℃壒鐘舵�佹灇涓�
- *
- * @author 瓒呯骇绠$悊鍛�
- */
-@RequiredArgsConstructor
-@Getter
-public enum ErpPurchaseInvoiceAuditStatusEnum implements ArrayValuable<Integer> {
-
-    DRAFT(0, "鏈彁浜�"),
-    PROCESS(10, "瀹℃壒涓�"),
-    APPROVE(20, "瀹℃牳閫氳繃"),
-    REJECT(30, "瀹℃牳涓嶉�氳繃"),
-    CANCEL(40, "宸插彇娑�");
-
-    public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpPurchaseInvoiceAuditStatusEnum::getStatus).toArray(Integer[]::new);
-
-    private final Integer status;
-    private final String name;
-
-    @Override
-    public Integer[] array() {
-        return ARRAYS;
-    }
-
-}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
index 64e984f..958f6fc 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErrorCodeConstants.java
@@ -57,14 +57,10 @@
     // ========== ERP 閲囪喘鏉ョエ锛�1-030-105-000锛� ==========
     ErrorCode PURCHASE_INVOICE_NOT_EXISTS = new ErrorCode(1_030_105_000, "鏉ョエ涓嶅瓨鍦�");
     ErrorCode PURCHASE_INVOICE_NO_EXISTS = new ErrorCode(1_030_105_001, "鐢熸垚鏉ョエ缂栧彿澶辫触锛岃閲嶆柊鎻愪氦");
-    ErrorCode PURCHASE_INVOICE_UPDATE_FAIL_EDITING_PROHIBITED = new ErrorCode(1_030_105_002, "鏉ョエ({})涓嶆槸鏈鏍哥姸鎬侊紝鏃犳硶淇敼");
-    ErrorCode PURCHASE_INVOICE_DELETE_FAIL_IS_APPROVE = new ErrorCode(1_030_105_003, "鍒犻櫎鏉ョエ澶辫触锛屽師鍥狅細鏉ョエ瀹℃壒宸查�氳繃");
-    ErrorCode PURCHASE_INVOICE_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_105_005, "鏇存柊鏉ョエ瀹℃牳鐘舵�佸け璐ワ紝鍘熷洜锛氭潵绁ㄤ笉鏄鏍镐腑鐘舵��");
+    ErrorCode PURCHASE_INVOICE_DELETE_FAIL_HAS_PAYMENT = new ErrorCode(1_030_105_004, "鍒犻櫎鏉ョエ澶辫触锛屽師鍥狅細鏉ョエ宸插瓨鍦ㄦ湁鏁堜粯娆惧崟");
     ErrorCode PURCHASE_INVOICE_CREATE_FAIL_ORDER_NOT_APPROVE = new ErrorCode(1_030_105_006, "鍒涘缓鏉ョエ澶辫触锛屽師鍥狅細閲囪喘璁㈠崟涓嶆槸瀹℃牳閫氳繃鐘舵��");
     ErrorCode PURCHASE_INVOICE_CREATE_FAIL_SUPPLIER_MISMATCH = new ErrorCode(1_030_105_007, "鍒涘缓鏉ョエ澶辫触锛屽師鍥狅細鏉ョエ渚涘簲鍟嗕笌閲囪喘璁㈠崟涓嶄竴鑷�");
     ErrorCode PURCHASE_INVOICE_CREATE_FAIL_PRICE_EXCEEDS_LIMIT = new ErrorCode(1_030_105_008, "鍒涘缓鏉ョエ澶辫触锛屽師鍥狅細鏉ョエ閲戦瓒呭嚭閲囪喘璁㈠崟閲戦锛岀洰鍓嶅彲寮�绁細{} 鍏�");
-    ErrorCode PURCHASE_INVOICE_APPROVE_FAIL = new ErrorCode(1_030_105_009, "瀹℃牳澶辫触锛屽彧鏈夋湭瀹℃牳鐨勬潵绁ㄦ墠鑳藉鏍�");
-    ErrorCode PURCHASE_INVOICE_PROCESS_FAIL = new ErrorCode(1_030_105_010, "鍙嶅鏍稿け璐ワ紝鍙湁宸插鏍哥殑鏉ョエ鎵嶈兘鍙嶅鏍�");
 
     // ========== ERP 閲囪喘閫�璐э紙1-030-103-000锛� ==========
     ErrorCode PURCHASE_RETURN_NOT_EXISTS = new ErrorCode(1_030_103_000, "閲囪喘閫�璐у崟涓嶅瓨鍦�");
@@ -193,7 +189,6 @@
     ErrorCode FINANCE_PAYMENT_NO_EXISTS = new ErrorCode(1_030_601_004, "鐢熸垚浠樻鍗曞彿澶辫触锛岃閲嶆柊鎻愪氦");
     ErrorCode FINANCE_PAYMENT_UPDATE_FAIL_APPROVE = new ErrorCode(1_030_601_005, "浠樻鍗�({})宸插鏍革紝鏃犳硶淇敼");
     ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_EXISTS = new ErrorCode(1_030_601_006, "鍒涘缓浠樻鍗曞け璐ワ紝鍘熷洜锛氬叧鑱旂殑鏉ョエ涓嶅瓨鍦�");
-    ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_APPROVE = new ErrorCode(1_030_601_007, "鍒涘缓浠樻鍗曞け璐ワ紝鍘熷洜锛氬叧鑱旂殑鏉ョエ涓嶆槸瀹℃壒閫氳繃鐘舵��");
     ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_ATTACHMENT = new ErrorCode(1_030_601_008, "鍒涘缓浠樻鍗曞け璐ワ紝鍘熷洜锛氬叧鑱旂殑鏉ョエ鏈笂浼犲彂绁ㄩ檮浠�");
     ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_SUPPLIER_MISMATCH = new ErrorCode(1_030_601_009, "鍒涘缓浠樻鍗曞け璐ワ紝鍘熷洜锛氬叧鑱旂殑鏉ョエ涓庢墍閫変緵搴斿晢涓嶅尮閰�");
     ErrorCode FINANCE_PAYMENT_CREATE_FAIL_INVOICE_PRICE_EXCEEDS = new ErrorCode(1_030_601_010, "鍒涘缓浠樻鍗曞け璐ワ紝鍘熷洜锛氫粯娆鹃噾棰濊秴鍑鸿鏉ョエ鍓╀綑鍙粯閲戦锛岀洰鍓嶅彲浠橈細{} 鍏�");
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
index 408d6d6..5fdd106 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/finance/ErpFinancePaymentServiceImpl.java
@@ -20,7 +20,6 @@
 import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper;
 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.ErpPurchaseInvoiceAuditStatusEnum;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceService;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseInvoiceServiceImpl;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpSupplierService;
@@ -354,7 +353,7 @@
     }
 
     /**
-     * 鏍¢獙浠樻鍗曞叧鑱旂殑鏉ョエ锛氬瓨鍦ㄣ�佸鎵归�氳繃銆佸凡涓婁紶鍙戠エ闄勪欢銆佷緵搴斿晢涓庢潵绁ㄤ竴鑷�
+     * 鏍¢獙浠樻鍗曞叧鑱旂殑鏉ョエ锛氬瓨鍦ㄣ�佸凡涓婁紶鍙戠エ闄勪欢銆佷緵搴斿晢涓庢潵绁ㄤ竴鑷�
      *
      * @param invoiceId 鏉ョエ缂栧彿
      * @param supplierId 渚涘簲鍟嗙紪鍙�
@@ -365,16 +364,12 @@
         if (invoice == null) {
             throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_EXISTS);
         }
-        // 2. 鏍¢獙鏉ョエ瀹℃壒閫氳繃
-        if (ObjectUtil.notEqual(invoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus())) {
-            throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_APPROVE);
-        }
-        // 3. 鏍¢獙宸蹭笂浼犲彂绁ㄩ檮浠�
+        // 2. 鏍¢獙宸蹭笂浼犲彂绁ㄩ檮浠�
         if (CollUtil.isEmpty(storageAttachmentApi.listAttachments(
                 ErpPurchaseInvoiceServiceImpl.PURCHASE_INVOICE_RECORD_TYPE, invoiceId))) {
             throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_NOT_ATTACHMENT);
         }
-        // 4. 鏍¢獙渚涘簲鍟嗕笌鏉ョエ涓�鑷达紙闃茶剰鏁版嵁锛�
+        // 3. 鏍¢獙渚涘簲鍟嗕笌鏉ョエ涓�鑷达紙闃茶剰鏁版嵁锛�
         if (ObjectUtil.notEqual(invoice.getSupplierId(), supplierId)) {
             throw exception(FINANCE_PAYMENT_CREATE_FAIL_INVOICE_SUPPLIER_MISMATCH);
         }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java
index 9e0650a..10ec261 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceService.java
@@ -42,14 +42,6 @@
     void deletePurchaseInvoice(Long id);
 
     /**
-     * 鏇存柊鏉ョエ鐨勭姸鎬侊紙瀹℃牳/鍙嶅鏍革級
-     *
-     * @param id 缂栧彿
-     * @param status 鐘舵��
-     */
-    void updatePurchaseInvoiceStatus(Long id, Integer status);
-
-    /**
      * 鑾峰緱鏉ョエ
      *
      * @param id 缂栧彿
@@ -84,28 +76,19 @@
     PageResult<ErpPurchaseInvoiceDO> getPurchaseInvoicePage(ErpPurchaseInvoicePageReqVO pageReqVO);
 
     /**
-     * 鏍规嵁閲囪喘璁㈠崟缂栧彿鍜屽鎵圭姸鎬佽幏寰楁潵绁ㄥ垪琛�
+     * 鏍规嵁閲囪喘璁㈠崟缂栧彿鑾峰緱鏉ョエ鍒楄〃
      *
      * @param orderId 閲囪喘璁㈠崟缂栧彿
-     * @param auditStatuses 瀹℃壒鐘舵�侀泦鍚�
      * @return 鏉ョエ鍒楄〃
      */
-    List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderIdAndStatus(Long orderId, Collection<Integer> auditStatuses);
+    List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderId(Long orderId);
 
     /**
-     * 鏍规嵁渚涘簲鍟嗙紪鍙峰拰瀹℃壒鐘舵�佽幏寰楁潵绁ㄥ垪琛�
+     * 鏍规嵁渚涘簲鍟嗙紪鍙疯幏寰楁潵绁ㄥ垪琛�
      *
      * @param supplierId 渚涘簲鍟嗙紪鍙�
-     * @param auditStatuses 瀹℃壒鐘舵�侀泦鍚�
      * @return 鏉ョエ鍒楄〃
      */
-    List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListBySupplierIdAndStatus(Long supplierId, Collection<Integer> auditStatuses);
-
-    /**
-     * 鑾峰緱寰呭鏍告潵绁ㄦ暟閲�
-     *
-     * @return 鏁伴噺
-     */
-    Long getAuditInvoiceCount();
+    List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListBySupplierId(Long supplierId);
 
 }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
index 19c6e7e..ab24aed 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseInvoiceServiceImpl.java
@@ -9,9 +9,9 @@
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.invoice.ErpPurchaseInvoiceSaveReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseInvoiceDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
+import cn.iocoder.yudao.module.erp.dal.mysql.finance.ErpFinancePaymentMapper;
 import cn.iocoder.yudao.module.erp.dal.mysql.purchase.ErpPurchaseInvoiceMapper;
 import cn.iocoder.yudao.module.erp.dal.redis.no.ErpNoRedisDAO;
-import cn.iocoder.yudao.module.erp.enums.ErpPurchaseInvoiceAuditStatusEnum;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
@@ -21,6 +21,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.getSumValue;
@@ -44,6 +45,9 @@
     private ErpPurchaseInvoiceMapper invoiceMapper;
 
     @Resource
+    private ErpFinancePaymentMapper financePaymentMapper;
+
+    @Resource
     private ErpNoRedisDAO noRedisDAO;
 
     @Resource
@@ -65,9 +69,8 @@
             throw exception(PURCHASE_INVOICE_NO_EXISTS);
         }
 
-        // 2.1 鎻掑叆鏉ョエ锛堝垱寤哄悗鍗充负鏈鏍哥姸鎬侊級
-        ErpPurchaseInvoiceDO invoice = BeanUtils.toBean(createReqVO, ErpPurchaseInvoiceDO.class)
-                .setNo(no).setAuditStatus(ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus());
+        // 2.1 鎻掑叆鏉ョエ锛堝垱寤哄悗鍗充负鏈夋晥鐘舵�侊紝鏃犻渶瀹℃牳锛�
+        ErpPurchaseInvoiceDO invoice = BeanUtils.toBean(createReqVO, ErpPurchaseInvoiceDO.class).setNo(no);
         invoiceMapper.insert(invoice);
 
         // 2.2 缁戝畾鍙戠エ闄勪欢
@@ -88,12 +91,9 @@
     }
 
     private void validatePurchaseInvoicePriceExceedsLimit(ErpPurchaseInvoiceSaveReqVO reqVO) {
-        // 1. 璁$畻鍓╀綑鍙紑绁ㄩ噾棰濓紝涓嶅寘鎷� reqVO 鑷韩锛堝鎵逛腑涔熷崰鐢ㄩ搴︼紝闃叉绱鏉ョエ瓒呰繃閲囪喘璁㈠崟閲戦锛�
+        // 1. 璁$畻鍓╀綑鍙紑绁ㄩ噾棰濓紝涓嶅寘鎷� reqVO 鑷韩锛堟墍鏈夋湁鏁堟潵绁ㄩ兘鍗犵敤棰濆害锛岄槻姝㈢疮璁℃潵绁ㄨ秴杩囬噰璐鍗曢噾棰濓級
         ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrder(reqVO.getPurchaseOrderId());
-        List<ErpPurchaseInvoiceDO> invoices = invoiceMapper.selectListByOrderIdAndStatus(reqVO.getPurchaseOrderId(),
-                List.of(ErpPurchaseInvoiceAuditStatusEnum.DRAFT.getStatus(),
-                        ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus(),
-                        ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus()));
+        List<ErpPurchaseInvoiceDO> invoices = invoiceMapper.selectListByOrderId(reqVO.getPurchaseOrderId());
         if (reqVO.getId() != null) {
             invoices.removeIf(invoice -> ObjectUtil.equal(invoice.getId(), reqVO.getId()));
         }
@@ -110,12 +110,8 @@
     public void updatePurchaseInvoice(ErpPurchaseInvoiceSaveReqVO updateReqVO) {
         Assert.notNull(updateReqVO.getId(), "鏉ョエ缂栧彿涓嶈兘涓虹┖");
         // 1.1 鏍¢獙瀛樺湪
-        ErpPurchaseInvoiceDO oldInvoice = validatePurchaseInvoiceExists(updateReqVO.getId());
-        // 1.2 鍙湁鏈鏍稿彲浠ョ紪杈�
-        if (ObjectUtil.notEqual(oldInvoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.PROCESS.getStatus())) {
-            throw exception(PURCHASE_INVOICE_UPDATE_FAIL_EDITING_PROHIBITED, oldInvoice.getNo());
-        }
-        // 1.3 鏍¢獙鍏宠仈鏁版嵁 + 鍙紑绁ㄩ噾棰�
+        validatePurchaseInvoiceExists(updateReqVO.getId());
+        // 1.2 鏍¢獙鍏宠仈鏁版嵁 + 鍙紑绁ㄩ噾棰�
         validateRelationDataExists(updateReqVO);
         validatePurchaseInvoicePriceExceedsLimit(updateReqVO);
 
@@ -131,35 +127,17 @@
     @Transactional(rollbackFor = Exception.class)
     public void deletePurchaseInvoice(Long id) {
         // 1.1 鏍¢獙瀛樺湪
-        ErpPurchaseInvoiceDO invoice = validatePurchaseInvoiceExists(id);
-        // 1.2 瀹℃壒閫氳繃鏃讹紝涓嶅厑璁稿垹闄�
-        if (ObjectUtil.equal(invoice.getAuditStatus(), ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus())) {
-            throw exception(PURCHASE_INVOICE_DELETE_FAIL_IS_APPROVE);
+        validatePurchaseInvoiceExists(id);
+        // 1.2 宸叉湁鏈夋晥浠樻鍗曞叧鑱旂殑鏉ョエ锛屼笉鍏佽鍒犻櫎锛堜繚鎸佹潵绁ㄤ笌浠樻鐨勫叧鑱斿畬鏁存�э級
+        Map<Long, BigDecimal> paidMap = financePaymentMapper.selectPaymentPriceMapByInvoiceId(List.of(id));
+        if (paidMap.getOrDefault(id, BigDecimal.ZERO).compareTo(BigDecimal.ZERO) > 0) {
+            throw exception(PURCHASE_INVOICE_DELETE_FAIL_HAS_PAYMENT);
         }
 
         // 2.1 鍒犻櫎鍙戠エ闄勪欢
         storageAttachmentApi.deleteAttachmentsByRecord(PURCHASE_INVOICE_RECORD_TYPE, id);
         // 2.2 鍒犻櫎鏉ョエ
         invoiceMapper.deleteById(id);
-    }
-
-    @Override
-    @Transactional(rollbackFor = Exception.class)
-    public void updatePurchaseInvoiceStatus(Long id, Integer status) {
-        boolean approve = ErpPurchaseInvoiceAuditStatusEnum.APPROVE.getStatus().equals(status);
-        // 1.1 鏍¢獙瀛樺湪
-        ErpPurchaseInvoiceDO invoice = validatePurchaseInvoiceExists(id);
-        // 1.2 鏍¢獙鐘舵��
-        if (invoice.getAuditStatus().equals(status)) {
-            throw exception(approve ? PURCHASE_INVOICE_APPROVE_FAIL : PURCHASE_INVOICE_PROCESS_FAIL);
-        }
-
-        // 2. 鏇存柊鐘舵��
-        int updateCount = invoiceMapper.updateByIdAndStatus(id, invoice.getAuditStatus(),
-                new ErpPurchaseInvoiceDO().setAuditStatus(status));
-        if (updateCount == 0) {
-            throw exception(approve ? PURCHASE_INVOICE_APPROVE_FAIL : PURCHASE_INVOICE_PROCESS_FAIL);
-        }
     }
 
     private ErpPurchaseInvoiceDO validatePurchaseInvoiceExists(Long id) {
@@ -189,18 +167,13 @@
     }
 
     @Override
-    public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderIdAndStatus(Long orderId, Collection<Integer> auditStatuses) {
-        return invoiceMapper.selectListByOrderIdAndStatus(orderId, auditStatuses);
+    public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListByOrderId(Long orderId) {
+        return invoiceMapper.selectListByOrderId(orderId);
     }
 
     @Override
-    public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListBySupplierIdAndStatus(Long supplierId, Collection<Integer> auditStatuses) {
-        return invoiceMapper.selectListBySupplierIdAndStatus(supplierId, auditStatuses);
-    }
-
-    @Override
-    public Long getAuditInvoiceCount() {
-        return invoiceMapper.selectCountByAudit();
+    public List<ErpPurchaseInvoiceDO> getPurchaseInvoiceListBySupplierId(Long supplierId) {
+        return invoiceMapper.selectListBySupplierId(supplierId);
     }
 
 }
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
index b1db5b2..ae8c13c 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -101,6 +101,7 @@
         // 2.1 鎻掑叆璁㈠崟
         ErpSaleOrderDO saleOrder = BeanUtils.toBean(createReqVO, ErpSaleOrderDO.class, in -> in
                 .setNo(no).setStatus(ErpAuditStatus.PROCESS.getStatus())
+                .setDepositPrice(ObjectUtil.defaultIfNull(createReqVO.getDepositPrice(), BigDecimal.ZERO))
                 .setOutCount(BigDecimal.ZERO).setOutStatus(ErpSaleOrderOutStatusEnum.NOT_OUT.getStatus()));
         calculateTotalPrice(saleOrder, saleOrderItems);
         saleOrderMapper.insert(saleOrder);
@@ -136,6 +137,7 @@
 
         // 2.1 鏇存柊璁㈠崟
         ErpSaleOrderDO updateObj = BeanUtils.toBean(updateReqVO, ErpSaleOrderDO.class);
+        updateObj.setDepositPrice(ObjectUtil.defaultIfNull(updateReqVO.getDepositPrice(), BigDecimal.ZERO));
         calculateTotalPrice(updateObj, saleOrderItems);
         saleOrderMapper.updateById(updateObj);
         // 2.2 鏇存柊璁㈠崟椤�
@@ -478,4 +480,4 @@
         }
     }
 
-}
\ No newline at end of file
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
index 11509cb..7154a10 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -143,6 +143,7 @@
         syncReqDTO.setMaxStock(reqVO.getMaxStock());
         syncReqDTO.setHighValue(reqVO.getIsHighValue());
         syncReqDTO.setBatchFlag(reqVO.getIsBatchManaged());
+        syncReqDTO.setStatus(reqVO.getStatus());
         syncReqDTO.setRemark(reqVO.getRemark());
         // 鎵规閰嶇疆瀛楁
         syncReqDTO.setProduceDateFlag(reqVO.getProduceDateFlag());
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java
index 0341052..1910fb0 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/dto/MesMdItemSyncReqDTO.java
@@ -42,6 +42,9 @@
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
+    @Schema(description = "鐘舵��", example = "1")
+    private Integer status;
+
     // ========== 鎵规灞炴�ч厤缃� ==========
 
     @Schema(description = "鐢熶骇鏃ユ湡蹇呭~", example = "true")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
new file mode 100644
index 0000000..531ccb3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
@@ -0,0 +1,145 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import cn.iocoder.yudao.module.mes.enums.dv.MesDvMeteringExpireStatusEnum;
+import cn.iocoder.yudao.module.mes.service.dv.metering.MesDvMeteringService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁¢噺鍣ㄥ叿")
+@RestController
+@RequestMapping("/mes/dv/metering")
+@Validated
+public class MesDvMeteringController {
+
+    @Resource
+    private MesDvMeteringService meteringService;
+    @Resource
+    private DeptApi deptApi;
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璁¢噺鍣ㄥ叿")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:create')")
+    public CommonResult<Long> createMetering(@Valid @RequestBody MesDvMeteringSaveReqVO createReqVO) {
+        return success(meteringService.createMetering(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璁¢噺鍣ㄥ叿")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:update')")
+    public CommonResult<Boolean> updateMetering(@Valid @RequestBody MesDvMeteringSaveReqVO updateReqVO) {
+        meteringService.updateMetering(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璁¢噺鍣ㄥ叿")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:delete')")
+    public CommonResult<Boolean> deleteMetering(@RequestParam("id") Long id) {
+        meteringService.deleteMetering(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璁¢噺鍣ㄥ叿")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:query')")
+    public CommonResult<MesDvMeteringRespVO> getMetering(@RequestParam("id") Long id) {
+        MesDvMeteringDO metering = meteringService.getMetering(id);
+        if (metering == null) {
+            return success(null);
+        }
+        return success(buildMeteringRespVOList(Collections.singletonList(metering)).get(0));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璁¢噺鍣ㄥ叿鍒嗛〉")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:query')")
+    public CommonResult<PageResult<MesDvMeteringRespVO>> getMeteringPage(@Valid MesDvMeteringPageReqVO pageReqVO) {
+        PageResult<MesDvMeteringDO> pageResult = meteringService.getMeteringPage(pageReqVO);
+        return success(new PageResult<>(buildMeteringRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭璁¢噺鍣ㄥ叿 Excel")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportMeteringExcel(@Valid MesDvMeteringPageReqVO pageReqVO,
+                                    HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<MesDvMeteringDO> list = meteringService.getMeteringPage(pageReqVO).getList();
+        ExcelUtils.write(response, "璁¢噺鍣ㄥ叿.xls", "鏁版嵁", MesDvMeteringRespVO.class,
+                buildMeteringRespVOList(list));
+    }
+
+    @GetMapping("/simple-list")
+    @Operation(summary = "鑾峰緱璁¢噺鍣ㄥ叿绮剧畝鍒楄〃", description = "涓昏鐢ㄤ簬鍓嶇鐨勪笅鎷夐�夐」")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:query')")
+    public CommonResult<List<MesDvMeteringRespVO>> getMeteringSimpleList() {
+        List<MesDvMeteringDO> list = meteringService.getMeteringList();
+        return success(buildMeteringRespVOList(list));
+    }
+
+    // ==================== 鎷兼帴 VO ====================
+
+    @SuppressWarnings("DuplicatedCode")
+    private List<MesDvMeteringRespVO> buildMeteringRespVOList(List<MesDvMeteringDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        // 1. 鎵归噺鑾峰彇鍏宠仈鏁版嵁
+        int remindDays = meteringService.getRemindDays();
+        Map<Long, DeptRespDTO> deptMap = CollUtil.isNotEmpty(convertSet(list, MesDvMeteringDO::getDeptId))
+                ? deptApi.getDeptMap(convertSet(list, MesDvMeteringDO::getDeptId)) : Collections.emptyMap();
+        Map<Long, AdminUserRespDTO> userMap = CollUtil.isNotEmpty(convertSet(list, MesDvMeteringDO::getChargeUserId))
+                ? adminUserApi.getUserMap(convertSet(list, MesDvMeteringDO::getChargeUserId)) : Collections.emptyMap();
+        // 2. 鎷兼帴 VO
+        return BeanUtils.toBean(list, MesDvMeteringRespVO.class, vo -> {
+            // 璁$畻鍒版湡鐘舵�佷笌鍓╀綑澶╂暟
+            vo.setExpireStatus(MesDvMeteringExpireStatusEnum.compute(vo.getNextCheckDate(), remindDays));
+            if (vo.getNextCheckDate() != null) {
+                vo.setExpireDays(ChronoUnit.DAYS.between(LocalDate.now(), vo.getNextCheckDate()));
+            }
+            MapUtils.findAndThen(deptMap, vo.getDeptId(), dept -> vo.setDeptName(dept.getName()));
+            MapUtils.findAndThen(userMap, vo.getChargeUserId(),
+                    user -> vo.setChargeUserNickname(user.getNickname()));
+        });
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringPageReqVO.java
new file mode 100644
index 0000000..be2cd57
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringPageReqVO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺鍣ㄥ叿鍒嗛〉 Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesDvMeteringPageReqVO extends PageParam {
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栫爜", example = "MD-001")
+    private String code;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿鍚嶇О", example = "鏁版樉鍗″昂")
+    private String name;
+
+    @Schema(description = "鍝佺墝/鐢熶骇鍘傚", example = "涓変赴")
+    private String brand;
+
+    @Schema(description = "鍣ㄥ叿绫诲埆", example = "1")
+    private Integer category;
+
+    @Schema(description = "浣跨敤閮ㄩ棬缂栧彿", example = "100")
+    private Long deptId;
+
+    @Schema(description = "璐d换浜虹紪鍙�", example = "200")
+    private Long chargeUserId;
+
+    @Schema(description = "鐘舵��", example = "1")
+    private Integer status;
+
+    @Schema(description = "鍒版湡鐘舵�侊紙1 宸茶繃鏈�/2 鍗冲皢鍒版湡/3 姝e父锛�", example = "2")
+    private Integer expireStatus;
+
+    @Schema(description = "涓嬫妫�娴嬫棩鏈熷紑濮嬶紙鐢卞悗绔牴鎹埌鏈熺姸鎬佽嚜鍔ㄥ~鍏咃級", hidden = true)
+    private LocalDate nextCheckDateBegin;
+
+    @Schema(description = "涓嬫妫�娴嬫棩鏈熺粨鏉燂紙鐢卞悗绔牴鎹埌鏈熺姸鎬佽嚜鍔ㄥ~鍏咃級", hidden = true)
+    private LocalDate nextCheckDateEnd;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringRespVO.java
new file mode 100644
index 0000000..f7ca46d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringRespVO.java
@@ -0,0 +1,103 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺鍣ㄥ叿 Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesDvMeteringRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "MD-001")
+    @ExcelProperty("璁¢噺鍣ㄥ叿缂栫爜")
+    private String code;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏁版樉鍗″昂")
+    @ExcelProperty("璁¢噺鍣ㄥ叿鍚嶇О")
+    private String name;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "0-150mm")
+    @ExcelProperty("瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @Schema(description = "鍝佺墝/鐢熶骇鍘傚", example = "涓変赴")
+    @ExcelProperty("鍝佺墝/鐢熶骇鍘傚")
+    private String brand;
+
+    @Schema(description = "鍣ㄥ叿绫诲埆", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "鍣ㄥ叿绫诲埆", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_DV_METERING_CATEGORY)
+    private Integer category;
+
+    @Schema(description = "绮惧害", example = "0.01mm")
+    @ExcelProperty("绮惧害")
+    private String precision;
+
+    @Schema(description = "閲忕▼", example = "0-150mm")
+    @ExcelProperty("閲忕▼")
+    private String range;
+
+    @Schema(description = "浣跨敤閮ㄩ棬缂栧彿", example = "100")
+    private Long deptId;
+
+    @Schema(description = "浣跨敤閮ㄩ棬鍚嶇О", example = "璐ㄦ閮�")
+    @ExcelProperty("浣跨敤閮ㄩ棬")
+    private String deptName;
+
+    @Schema(description = "璐d换浜虹紪鍙�", example = "200")
+    private Long chargeUserId;
+
+    @Schema(description = "璐d换浜哄悕绉�", example = "寮犱笁")
+    @ExcelProperty("璐d换浜�")
+    private String chargeUserNickname;
+
+    @Schema(description = "鏍″噯鍛ㄦ湡锛堟湀锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+    @ExcelProperty("鏍″噯鍛ㄦ湡(鏈�)")
+    private Integer checkCycleCount;
+
+    @Schema(description = "鎶曞叆浣跨敤鏃ユ湡")
+    @ExcelProperty("鎶曞叆浣跨敤鏃ユ湡")
+    private LocalDate useDate;
+
+    @Schema(description = "涓婃妫�娴嬫棩鏈�")
+    @ExcelProperty("涓婃妫�娴嬫棩鏈�")
+    private LocalDate lastCheckDate;
+
+    @Schema(description = "涓嬫妫�娴嬫棩鏈燂紙鍒版湡鏃ワ級")
+    @ExcelProperty("涓嬫妫�娴嬫棩鏈�")
+    private LocalDate nextCheckDate;
+
+    @Schema(description = "鍒版湡鐘舵�侊紙1 宸茶繃鏈�/2 鍗冲皢鍒版湡/3 姝e父锛�", example = "2")
+    @ExcelProperty("鍒版湡鐘舵��")
+    private Integer expireStatus;
+
+    @Schema(description = "璺濈鍒版湡澶╂暟锛堟鏁版湭鍒帮紝璐熸暟宸茶繃鏈燂級", example = "10")
+    @ExcelProperty("鍓╀綑澶╂暟")
+    private Long expireDays;
+
+    @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_DV_METERING_STATUS)
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringSaveReqVO.java
new file mode 100644
index 0000000..d461ec1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringSaveReqVO.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺鍣ㄥ叿鏂板/淇敼 Request VO")
+@Data
+public class MesDvMeteringSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED, example = "MD-001")
+    @NotEmpty(message = "璁¢噺鍣ㄥ叿缂栫爜涓嶈兘涓虹┖")
+    private String code;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏁版樉鍗″昂")
+    @NotEmpty(message = "璁¢噺鍣ㄥ叿鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "瑙勬牸鍨嬪彿", example = "0-150mm")
+    private String specification;
+
+    @Schema(description = "鍝佺墝/鐢熶骇鍘傚", example = "涓変赴")
+    private String brand;
+
+    @Schema(description = "鍣ㄥ叿绫诲埆", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鍣ㄥ叿绫诲埆涓嶈兘涓虹┖")
+    private Integer category;
+
+    @Schema(description = "绮惧害", example = "0.01mm")
+    private String precision;
+
+    @Schema(description = "閲忕▼", example = "0-150mm")
+    private String range;
+
+    @Schema(description = "浣跨敤閮ㄩ棬缂栧彿", example = "100")
+    private Long deptId;
+
+    @Schema(description = "璐d换浜虹紪鍙�", example = "200")
+    private Long chargeUserId;
+
+    @Schema(description = "鏍″噯鍛ㄦ湡锛堟湀锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
+    @NotNull(message = "鏍″噯鍛ㄦ湡涓嶈兘涓虹┖")
+    private Integer checkCycleCount;
+
+    @Schema(description = "鎶曞叆浣跨敤鏃ユ湡")
+    private LocalDate useDate;
+
+    @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java
new file mode 100644
index 0000000..332d12d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/MesDvMeteringCheckController.java
@@ -0,0 +1,130 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckRespVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.meteringcheck.MesDvMeteringCheckDO;
+import cn.iocoder.yudao.module.mes.service.dv.metering.MesDvMeteringService;
+import cn.iocoder.yudao.module.mes.service.dv.meteringcheck.MesDvMeteringCheckService;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.io.IOException;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+
+@Tag(name = "绠$悊鍚庡彴 - MES 璁¢噺妫�娴嬭褰�")
+@RestController
+@RequestMapping("/mes/dv/metering-check")
+@Validated
+public class MesDvMeteringCheckController {
+
+    @Resource
+    private MesDvMeteringCheckService meteringCheckService;
+    @Resource
+    private MesDvMeteringService meteringService;
+    @Resource
+    private AdminUserApi adminUserApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓璁¢噺妫�娴嬭褰�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:create')")
+    public CommonResult<Long> createMeteringCheck(@Valid @RequestBody MesDvMeteringCheckSaveReqVO createReqVO) {
+        return success(meteringCheckService.createMeteringCheck(createReqVO));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊璁¢噺妫�娴嬭褰�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:update')")
+    public CommonResult<Boolean> updateMeteringCheck(@Valid @RequestBody MesDvMeteringCheckSaveReqVO updateReqVO) {
+        meteringCheckService.updateMeteringCheck(updateReqVO);
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璁¢噺妫�娴嬭褰�")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:delete')")
+    public CommonResult<Boolean> deleteMeteringCheck(@RequestParam("id") Long id) {
+        meteringCheckService.deleteMeteringCheck(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱璁¢噺妫�娴嬭褰�")
+    @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:query')")
+    public CommonResult<MesDvMeteringCheckRespVO> getMeteringCheck(@RequestParam("id") Long id) {
+        MesDvMeteringCheckDO check = meteringCheckService.getMeteringCheck(id);
+        if (check == null) {
+            return success(null);
+        }
+        return success(buildMeteringCheckRespVOList(Collections.singletonList(check)).get(0));
+    }
+
+    @GetMapping("/page")
+    @Operation(summary = "鑾峰緱璁¢噺妫�娴嬭褰曞垎椤�")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:query')")
+    public CommonResult<PageResult<MesDvMeteringCheckRespVO>> getMeteringCheckPage(
+            @Valid MesDvMeteringCheckPageReqVO pageReqVO) {
+        PageResult<MesDvMeteringCheckDO> pageResult = meteringCheckService.getMeteringCheckPage(pageReqVO);
+        return success(new PageResult<>(buildMeteringCheckRespVOList(pageResult.getList()), pageResult.getTotal()));
+    }
+
+    @GetMapping("/export-excel")
+    @Operation(summary = "瀵煎嚭璁¢噺妫�娴嬭褰� Excel")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering-check:export')")
+    @ApiAccessLog(operateType = EXPORT)
+    public void exportMeteringCheckExcel(@Valid MesDvMeteringCheckPageReqVO pageReqVO,
+                                         HttpServletResponse response) throws IOException {
+        pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
+        List<MesDvMeteringCheckDO> list = meteringCheckService.getMeteringCheckPage(pageReqVO).getList();
+        ExcelUtils.write(response, "璁¢噺妫�娴嬭褰�.xls", "鏁版嵁", MesDvMeteringCheckRespVO.class,
+                buildMeteringCheckRespVOList(list));
+    }
+
+    // ==================== 鎷兼帴 VO ====================
+
+    @SuppressWarnings("DuplicatedCode")
+    private List<MesDvMeteringCheckRespVO> buildMeteringCheckRespVOList(List<MesDvMeteringCheckDO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return Collections.emptyList();
+        }
+        // 1. 鎵归噺鑾峰彇鍏宠仈鏁版嵁
+        Map<Long, MesDvMeteringDO> meteringMap = meteringService.getMeteringMap(
+                convertSet(list, MesDvMeteringCheckDO::getMeteringId));
+        Map<Long, AdminUserRespDTO> userMap = CollUtil.isNotEmpty(convertSet(list, MesDvMeteringCheckDO::getCheckUserId))
+                ? adminUserApi.getUserMap(convertSet(list, MesDvMeteringCheckDO::getCheckUserId)) : Collections.emptyMap();
+        // 2. 鎷兼帴 VO
+        return BeanUtils.toBean(list, MesDvMeteringCheckRespVO.class, vo -> {
+            MapUtils.findAndThen(meteringMap, vo.getMeteringId(),
+                    metering -> vo.setMeteringCode(metering.getCode()).setMeteringName(metering.getName()));
+            MapUtils.findAndThen(userMap, vo.getCheckUserId(),
+                    user -> vo.setCheckUserNickname(user.getNickname()));
+        });
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckPageReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckPageReqVO.java
new file mode 100644
index 0000000..8176fda
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckPageReqVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+import lombok.ToString;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺妫�娴嬭褰曞垎椤� Request VO")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+public class MesDvMeteringCheckPageReqVO extends PageParam {
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栧彿", example = "100")
+    private Long meteringId;
+
+    @Schema(description = "妫�娴嬬紪鍙�", example = "CK-001")
+    private String checkCode;
+
+    @Schema(description = "妫�娴嬬粨璁�", example = "1")
+    private Integer checkResult;
+
+    @Schema(description = "妫�娴嬫棩鏈�")
+    private LocalDate[] checkDate;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckRespVO.java
new file mode 100644
index 0000000..c3b21c6
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckRespVO.java
@@ -0,0 +1,74 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo;
+
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺妫�娴嬭褰� Response VO")
+@Data
+@ExcelIgnoreUnannotated
+public class MesDvMeteringCheckRespVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    @ExcelProperty("缂栧彿")
+    private Long id;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    private Long meteringId;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栫爜", example = "MD-001")
+    @ExcelProperty("璁¢噺鍣ㄥ叿缂栫爜")
+    private String meteringCode;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿鍚嶇О", example = "鏁版樉鍗″昂")
+    @ExcelProperty("璁¢噺鍣ㄥ叿鍚嶇О")
+    private String meteringName;
+
+    @Schema(description = "妫�娴嬬紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "CK-001")
+    @ExcelProperty("妫�娴嬬紪鍙�")
+    private String checkCode;
+
+    @Schema(description = "妫�娴嬫棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("妫�娴嬫棩鏈�")
+    private LocalDate checkDate;
+
+    @Schema(description = "妫�娴嬫満鏋�", example = "甯傝閲忛櫌")
+    @ExcelProperty("妫�娴嬫満鏋�")
+    private String checkOrg;
+
+    @Schema(description = "妫�娴嬬粨璁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty(value = "妫�娴嬬粨璁�", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_DV_METERING_CHECK_RESULT)
+    private Integer checkResult;
+
+    @Schema(description = "璇佷功缂栧彿", example = "CN-2026-001")
+    @ExcelProperty("璇佷功缂栧彿")
+    private String certNo;
+
+    @Schema(description = "妫�娴嬩汉缂栧彿", example = "200")
+    private Long checkUserId;
+
+    @Schema(description = "妫�娴嬩汉鍚嶇О", example = "鏉庡洓")
+    @ExcelProperty("妫�娴嬩汉")
+    private String checkUserNickname;
+
+    @Schema(description = "涓嬫妫�娴嬫棩鏈�")
+    @ExcelProperty("涓嬫妫�娴嬫棩鏈�")
+    private LocalDate nextCheckDate;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckSaveReqVO.java
new file mode 100644
index 0000000..cb2ebc1
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/meteringcheck/vo/MesDvMeteringCheckSaveReqVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotEmpty;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.time.LocalDate;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 璁¢噺妫�娴嬭褰曟柊澧�/淇敼 Request VO")
+@Data
+public class MesDvMeteringCheckSaveReqVO {
+
+    @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
+    private Long id;
+
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
+    @NotNull(message = "璁¢噺鍣ㄥ叿涓嶈兘涓虹┖")
+    private Long meteringId;
+
+    @Schema(description = "妫�娴嬬紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "CK-001")
+    @NotEmpty(message = "妫�娴嬬紪鍙蜂笉鑳戒负绌�")
+    private String checkCode;
+
+    @Schema(description = "妫�娴嬫棩鏈�", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "妫�娴嬫棩鏈熶笉鑳戒负绌�")
+    private LocalDate checkDate;
+
+    @Schema(description = "妫�娴嬫満鏋�", example = "甯傝閲忛櫌")
+    private String checkOrg;
+
+    @Schema(description = "妫�娴嬬粨璁�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "妫�娴嬬粨璁轰笉鑳戒负绌�")
+    private Integer checkResult;
+
+    @Schema(description = "璇佷功缂栧彿", example = "CN-2026-001")
+    private String certNo;
+
+    @Schema(description = "妫�娴嬩汉缂栧彿", example = "200")
+    private Long checkUserId;
+
+    @Schema(description = "涓嬫妫�娴嬫棩鏈燂紙涓虹┖鍒欐寜 妫�娴嬫棩鏈� + 鏍″噯鍛ㄦ湡 鑷姩璁$畻锛�")
+    private LocalDate nextCheckDate;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemController.java
index 1e58218..fdad22a 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemController.java
@@ -19,7 +19,6 @@
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
-import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
 import jakarta.servlet.http.HttpServletResponse;
@@ -62,19 +61,6 @@
     @PreAuthorize("@ss.hasPermission('mes:md-item:update')")
     public CommonResult<Boolean> updateItem(@Valid @RequestBody MesMdItemSaveReqVO updateReqVO) {
         itemService.updateItem(updateReqVO);
-        return success(true);
-    }
-
-    @PutMapping("/update-status")
-    @Operation(summary = "鏇存柊鐗╂枡浜у搧鐘舵��")
-    @Parameters({
-            @Parameter(name = "id", description = "缂栧彿", required = true),
-            @Parameter(name = "status", description = "鐘舵��", required = true)
-    })
-    @PreAuthorize("@ss.hasPermission('mes:md-item:update')")
-    public CommonResult<Boolean> updateItemStatus(@RequestParam("id") Long id,
-                                                  @RequestParam("status") Integer status) {
-        itemService.updateItemStatus(id, status);
         return success(true);
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
index e6ac294..6de4a79 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/MesMdItemSaveReqVO.java
@@ -51,6 +51,9 @@
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
+    @Schema(description = "鐘舵��", example = "1")
+    private Integer status;
+
     @Schema(description = "鏄惁鍚屾鍒� MDM", example = "true")
     private Boolean syncMdm;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
index 88b79d1..45c7cb9 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/MesPdProjectController.java
+++ b/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)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java
index d0ea908..28a581f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/project/vo/MesPdProjectRespVO.java
+++ b/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;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java
index 5a9257e..6fff993 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/MesWmBatchController.java
+++ b/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;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchProcessTraceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchProcessTraceRespVO.java
new file mode 100644
index 0000000..ce165eb
--- /dev/null
+++ b/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 = "浜嬩欢绫诲瀷锛欼N-宸ュ簭鎶曞叆 / 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;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchPurchaseSourceRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchPurchaseSourceRespVO.java
new file mode 100644
index 0000000..ddbd9e9
--- /dev/null
+++ b/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 = "渚涘簲鍟咥")
+    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;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/batch/vo/MesWmBatchSalesTargetRespVO.java
new file mode 100644
index 0000000..5da84ae
--- /dev/null
+++ b/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;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/metering/MesDvMeteringDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/metering/MesDvMeteringDO.java
new file mode 100644
index 0000000..7b6770b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/metering/MesDvMeteringDO.java
@@ -0,0 +1,100 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿 DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_dv_metering")
+@KeySequence("mes_dv_metering_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDvMeteringDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 璁¢噺鍣ㄥ叿缂栫爜
+     */
+    private String code;
+    /**
+     * 璁¢噺鍣ㄥ叿鍚嶇О
+     */
+    private String name;
+    /**
+     * 瑙勬牸鍨嬪彿
+     */
+    private String specification;
+    /**
+     * 鍝佺墝/鐢熶骇鍘傚
+     */
+    private String brand;
+    /**
+     * 鍣ㄥ叿绫诲埆
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_DV_METERING_CATEGORY}
+     */
+    private Integer category;
+    /**
+     * 绮惧害
+     */
+    @TableField("`precision`")
+    private String precision;
+    /**
+     * 閲忕▼
+     */
+    @TableField("`range`")
+    private String range;
+    /**
+     * 浣跨敤閮ㄩ棬缂栧彿
+     */
+    private Long deptId;
+    /**
+     * 璐d换浜虹紪鍙�
+     */
+    private Long chargeUserId;
+    /**
+     * 鏍″噯鍛ㄦ湡锛堟湀锛�
+     */
+    private Integer checkCycleCount;
+    /**
+     * 鎶曞叆浣跨敤鏃ユ湡
+     */
+    private LocalDate useDate;
+    /**
+     * 涓婃妫�娴嬫棩鏈�
+     */
+    private LocalDate lastCheckDate;
+    /**
+     * 涓嬫妫�娴嬫棩鏈燂紙鍒版湡鏃ワ級
+     */
+    private LocalDate nextCheckDate;
+    /**
+     * 鐘舵��
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_DV_METERING_STATUS}
+     */
+    private Integer status;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/meteringcheck/MesDvMeteringCheckDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/meteringcheck/MesDvMeteringCheckDO.java
new file mode 100644
index 0000000..4a6847b
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/dv/meteringcheck/MesDvMeteringCheckDO.java
@@ -0,0 +1,73 @@
+package cn.iocoder.yudao.module.mes.dal.dataobject.dv.meteringcheck;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.time.LocalDate;
+
+/**
+ * MES 璁¢噺妫�娴嬭褰� DO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("mes_dv_metering_check")
+@KeySequence("mes_dv_metering_check_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class MesDvMeteringCheckDO extends BaseDO {
+
+    /**
+     * 缂栧彿
+     */
+    @TableId
+    private Long id;
+    /**
+     * 璁¢噺鍣ㄥ叿缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO#getId()}
+     */
+    private Long meteringId;
+    /**
+     * 妫�娴嬬紪鍙�
+     */
+    private String checkCode;
+    /**
+     * 妫�娴嬫棩鏈�
+     */
+    private LocalDate checkDate;
+    /**
+     * 妫�娴嬫満鏋�
+     */
+    private String checkOrg;
+    /**
+     * 妫�娴嬬粨璁�
+     *
+     * 瀛楀吀 {@link DictTypeConstants#MES_DV_METERING_CHECK_RESULT}
+     */
+    private Integer checkResult;
+    /**
+     * 璇佷功缂栧彿
+     */
+    private String certNo;
+    /**
+     * 妫�娴嬩汉缂栧彿
+     */
+    private Long checkUserId;
+    /**
+     * 涓嬫妫�娴嬫棩鏈燂紙鑷姩璁$畻鎴栨墜鍔ㄦ寚瀹氾級
+     */
+    private LocalDate nextCheckDate;
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java
index c6f449e..bbfd926 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/project/MesPdProjectDO.java
+++ b/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()
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java
new file mode 100644
index 0000000..a4078de
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.dv.metering;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.dv.metering.vo.MesDvMeteringPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface MesDvMeteringMapper extends BaseMapperX<MesDvMeteringDO> {
+
+    default PageResult<MesDvMeteringDO> selectPage(MesDvMeteringPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesDvMeteringDO>()
+                .likeIfPresent(MesDvMeteringDO::getCode, reqVO.getCode())
+                .likeIfPresent(MesDvMeteringDO::getName, reqVO.getName())
+                .likeIfPresent(MesDvMeteringDO::getBrand, reqVO.getBrand())
+                .eqIfPresent(MesDvMeteringDO::getCategory, reqVO.getCategory())
+                .eqIfPresent(MesDvMeteringDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(MesDvMeteringDO::getChargeUserId, reqVO.getChargeUserId())
+                .eqIfPresent(MesDvMeteringDO::getStatus, reqVO.getStatus())
+                .geIfPresent(MesDvMeteringDO::getNextCheckDate, reqVO.getNextCheckDateBegin())
+                .leIfPresent(MesDvMeteringDO::getNextCheckDate, reqVO.getNextCheckDateEnd())
+                .orderByDesc(MesDvMeteringDO::getId));
+    }
+
+    default MesDvMeteringDO selectByCode(String code) {
+        return selectOne(MesDvMeteringDO::getCode, code);
+    }
+
+    default List<MesDvMeteringDO> selectListByNextCheckDateBetween(LocalDate begin, LocalDate end) {
+        return selectList(new LambdaQueryWrapperX<MesDvMeteringDO>()
+                .ge(MesDvMeteringDO::getNextCheckDate, begin)
+                .le(MesDvMeteringDO::getNextCheckDate, end));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/meteringcheck/MesDvMeteringCheckMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/meteringcheck/MesDvMeteringCheckMapper.java
new file mode 100644
index 0000000..5ccf30e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/meteringcheck/MesDvMeteringCheckMapper.java
@@ -0,0 +1,43 @@
+package cn.iocoder.yudao.module.mes.dal.mysql.dv.meteringcheck;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+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.dv.meteringcheck.vo.MesDvMeteringCheckPageReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.meteringcheck.MesDvMeteringCheckDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * MES 璁¢噺妫�娴嬭褰� Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface MesDvMeteringCheckMapper extends BaseMapperX<MesDvMeteringCheckDO> {
+
+    default PageResult<MesDvMeteringCheckDO> selectPage(MesDvMeteringCheckPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<MesDvMeteringCheckDO>()
+                .eqIfPresent(MesDvMeteringCheckDO::getMeteringId, reqVO.getMeteringId())
+                .likeIfPresent(MesDvMeteringCheckDO::getCheckCode, reqVO.getCheckCode())
+                .eqIfPresent(MesDvMeteringCheckDO::getCheckResult, reqVO.getCheckResult())
+                .betweenIfPresent(MesDvMeteringCheckDO::getCheckDate, reqVO.getCheckDate())
+                .orderByDesc(MesDvMeteringCheckDO::getId));
+    }
+
+    default MesDvMeteringCheckDO selectByCheckCode(String checkCode) {
+        return selectOne(MesDvMeteringCheckDO::getCheckCode, checkCode);
+    }
+
+    default MesDvMeteringCheckDO selectLatestByMeteringId(Long meteringId) {
+        return selectOne(new LambdaQueryWrapperX<MesDvMeteringCheckDO>()
+                .eq(MesDvMeteringCheckDO::getMeteringId, meteringId)
+                .orderByDesc(MesDvMeteringCheckDO::getCheckDate)
+                .orderByDesc(MesDvMeteringCheckDO::getId)
+                .last("LIMIT 1"));
+    }
+
+    default Long selectCountByMeteringId(Long meteringId) {
+        return selectCount(MesDvMeteringCheckDO::getMeteringId, meteringId);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
index 8294d56..084e2ad 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
+++ b/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);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
index ca2bc14..ca7c0fe 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
@@ -15,4 +15,20 @@
      */
     String AUTO_CODE = "mes:md:auto_code:";
 
+    /**
+     * 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋鍘婚噸
+     *
+     * KEY 鏍煎紡锛歮es:dv:metering:remind:{meteringId}:{nextCheckDate}
+     * VALUE 鏁版嵁鏍煎紡锛�1
+     */
+    String METERING_EXPIRE_REMIND = "mes:dv:metering:remind:";
+
+    /**
+     * 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋鎵弿闂ㄩ棭
+     *
+     * KEY 鏍煎紡锛歮es:dv:metering:remind:scan:{today}
+     * VALUE 鏁版嵁鏍煎紡锛�1
+     */
+    String METERING_EXPIRE_REMIND_SCAN = "mes:dv:metering:remind:scan:";
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java
new file mode 100644
index 0000000..a12e6f7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.mes.dal.redis.dv.metering;
+
+import cn.iocoder.yudao.module.mes.dal.redis.RedisKeyConstants;
+import jakarta.annotation.Resource;
+import org.springframework.data.redis.core.StringRedisTemplate;
+import org.springframework.stereotype.Repository;
+
+import java.time.Duration;
+import java.time.LocalDate;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋 Redis DAO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Repository
+public class MesDvMeteringRemindRedisDAO {
+
+    /**
+     * 鎻愰啋鍘婚噸 KEY 鐨勮繃鏈熸椂闂达紙60 澶╋級锛屽悓涓�鍣ㄥ叿鍚屼竴鍒版湡鏃� 60 澶╁唴鍙彁閱掍竴娆�
+     */
+    private static final Duration REMIND_TIMEOUT = Duration.ofDays(60);
+    /**
+     * 鎵弿闂ㄩ棭鐨勮繃鏈熸椂闂达紙30 鍒嗛挓锛夛紝閬垮厤澶氬疄渚�/澶氭骞跺彂閲嶅鎵弿
+     */
+    private static final Duration SCAN_LOCK_TIMEOUT = Duration.ofMinutes(30);
+
+    @Resource
+    private StringRedisTemplate stringRedisTemplate;
+
+    /**
+     * 灏濊瘯鏍囪宸叉彁閱掞紙鍚屼竴鍣ㄥ叿鍚屼竴鍒版湡鏃� 60 澶╁唴鍙彁閱掍竴娆★級
+     *
+     * @param meteringId 璁¢噺鍣ㄥ叿缂栧彿
+     * @param nextCheckDate 鍒版湡鏃�
+     * @return true 琛ㄧず棣栨鏍囪鎴愬姛锛岄渶瑕佸彂閫佹彁閱�
+     */
+    public boolean tryMarkReminded(Long meteringId, LocalDate nextCheckDate) {
+        String key = RedisKeyConstants.METERING_EXPIRE_REMIND + meteringId + ":" + nextCheckDate;
+        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", REMIND_TIMEOUT));
+    }
+
+    /**
+     * 灏濊瘯鑾峰彇褰撳ぉ鐨勬壂鎻忛棬闂╋紝閬垮厤澶氬疄渚嬪苟鍙戦噸澶嶆壂鎻�
+     *
+     * @param today 褰撳ぉ鏃ユ湡
+     * @return true 琛ㄧず鑾峰彇鎴愬姛锛堝彲浠ユ墽琛屾壂鎻忥級
+     */
+    public boolean tryAcquireScanLock(LocalDate today) {
+        String key = RedisKeyConstants.METERING_EXPIRE_REMIND_SCAN + today;
+        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", SCAN_LOCK_TIMEOUT));
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
index 6cf7ede..0fd3d74 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/DictTypeConstants.java
@@ -93,6 +93,9 @@
     String MES_DV_CHECK_RESULT = "mes_dv_check_result"; // MES 鐐规缁撴灉
     String MES_DV_REPAIR_STATUS = "mes_dv_repair_status"; // MES 缁翠慨宸ュ崟鐘舵��
     String MES_DV_REPAIR_RESULT = "mes_dv_repair_result"; // MES 缁翠慨缁撴灉
+    String MES_DV_METERING_CATEGORY = "mes_dv_metering_category"; // MES 璁¢噺鍣ㄥ叿绫诲埆
+    String MES_DV_METERING_STATUS = "mes_dv_metering_status"; // MES 璁¢噺鍣ㄥ叿鐘舵��
+    String MES_DV_METERING_CHECK_RESULT = "mes_dv_metering_check_result"; // MES 璁¢噺妫�娴嬬粨璁�
 
     // ========== 鎺掔彮鏃ュ巻 (CAL) ==========
     String MES_CAL_HOLIDAY_TYPE = "mes_cal_holiday_type"; // MES 鍋囨湡绫诲瀷
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 368ebf4..dec84a2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -215,6 +215,15 @@
     // ========== MES 璁惧绠$悊-鐐规璁板綍鏄庣粏锛�1-040-306-100锛� ==========
     ErrorCode DV_CHECK_RECORD_LINE_NOT_EXISTS = new ErrorCode(1_040_306_100, "璁惧鐐规璁板綍鏄庣粏涓嶅瓨鍦�");
 
+    // ========== MES 璁惧绠$悊-璁¢噺鍣ㄥ叿锛�1-040-307-000锛� ==========
+    ErrorCode DV_METERING_NOT_EXISTS = new ErrorCode(1_040_307_000, "璁¢噺鍣ㄥ叿涓嶅瓨鍦�");
+    ErrorCode DV_METERING_CODE_DUPLICATE = new ErrorCode(1_040_307_001, "璁¢噺鍣ㄥ叿缂栫爜宸插瓨鍦�");
+    ErrorCode DV_METERING_HAS_CHECK_RECORD = new ErrorCode(1_040_307_002, "璁¢噺鍣ㄥ叿宸插叧鑱旀娴嬭褰曪紝鏃犳硶鍒犻櫎");
+
+    // ========== MES 璁惧绠$悊-璁¢噺妫�娴嬭褰曪紙1-040-307-100锛� ==========
+    ErrorCode DV_METERING_CHECK_NOT_EXISTS = new ErrorCode(1_040_307_100, "璁¢噺妫�娴嬭褰曚笉瀛樺湪");
+    ErrorCode DV_METERING_CHECK_CODE_DUPLICATE = new ErrorCode(1_040_307_101, "璁¢噺妫�娴嬬紪鍙峰凡瀛樺湪");
+
     // ========== MES 宸ュ叿绠$悊-宸ュ叿绫诲瀷锛�1-040-400-000锛� ==========
     ErrorCode TM_TOOL_TYPE_NOT_EXISTS = new ErrorCode(1_040_400_000, "宸ュ叿绫诲瀷涓嶅瓨鍦�");
     ErrorCode TM_TOOL_TYPE_CODE_DUPLICATE = new ErrorCode(1_040_400_001, "宸ュ叿绫诲瀷缂栫爜宸插瓨鍦�");
@@ -767,6 +776,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, "鍙湁寰呭綊妗g姸鎬佺殑璁捐椤圭洰鎵嶈兘褰掓。");
     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, "璁捐璧勬枡涓嶅瓨鍦�");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java
index 1771e05..5b763af 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/MesPdStatusConstants.java
+++ b/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;
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringCheckResultEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringCheckResultEnum.java
new file mode 100644
index 0000000..cdc4d3d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringCheckResultEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.mes.enums.dv;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 璁¢噺妫�娴嬬粨璁烘灇涓�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesDvMeteringCheckResultEnum implements ArrayValuable<Integer> {
+
+    QUALIFIED(1, "鍚堟牸"),
+    UNQUALIFIED(2, "涓嶅悎鏍�");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesDvMeteringCheckResultEnum::getResult).toArray(Integer[]::new);
+
+    /**
+     * 缁撹鍊�
+     */
+    private final Integer result;
+    /**
+     * 缁撹鍚�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringExpireStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringExpireStatusEnum.java
new file mode 100644
index 0000000..76d1040
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringExpireStatusEnum.java
@@ -0,0 +1,61 @@
+package cn.iocoder.yudao.module.mes.enums.dv;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.time.LocalDate;
+import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿鍒版湡鐘舵�佹灇涓�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesDvMeteringExpireStatusEnum implements ArrayValuable<Integer> {
+
+    OVERDUE(1, "宸茶繃鏈�"),
+    WITHIN(2, "鍗冲皢鍒版湡"),
+    NORMAL(3, "姝e父");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesDvMeteringExpireStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵�佸��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String name;
+
+    /**
+     * 璁$畻鍒版湡鐘舵��
+     *
+     * @param nextCheckDate 涓嬫妫�娴嬫棩鏈燂紙鍒版湡鏃ワ級
+     * @param remindDays 鎻愬墠鎻愰啋澶╂暟
+     * @return 鍒版湡鐘舵��
+     */
+    public static Integer compute(LocalDate nextCheckDate, int remindDays) {
+        if (nextCheckDate == null) {
+            return NORMAL.getStatus();
+        }
+        long days = ChronoUnit.DAYS.between(LocalDate.now(), nextCheckDate);
+        if (days < 0) {
+            return OVERDUE.getStatus();
+        }
+        if (days <= remindDays) {
+            return WITHIN.getStatus();
+        }
+        return NORMAL.getStatus();
+    }
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringStatusEnum.java
new file mode 100644
index 0000000..4b26581
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/dv/MesDvMeteringStatusEnum.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.mes.enums.dv;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+import java.util.Arrays;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿鐘舵�佹灇涓�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Getter
+@AllArgsConstructor
+public enum MesDvMeteringStatusEnum implements ArrayValuable<Integer> {
+
+    IN_USE(1, "鍦ㄧ敤"),
+    STOPPED(2, "鍋滅敤"),
+    SCRAPPED(3, "鎶ュ簾");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values()).map(MesDvMeteringStatusEnum::getStatus).toArray(Integer[]::new);
+
+    /**
+     * 鐘舵�佸��
+     */
+    private final Integer status;
+    /**
+     * 鐘舵�佸悕
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java
index 94fd5e8..8aa13ec 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/pd/MesPdProjectStatusEnum.java
@@ -39,7 +39,7 @@
     /**
      * 寰呭綊妗�
      *
-     * 瀵瑰簲 MesPdProjectService#auditProject 鏂规硶
+     * 瀵瑰簲 MesPdProjectService#updateProjectAuditStatus 鏂规硶锛圔PM 瀹℃壒閫氳繃鍚庯級
      */
     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);
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindService.java
new file mode 100644
index 0000000..ea98a8d
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindService.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.mes.service.dv.metering;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesDvMeteringRemindService {
+
+    /**
+     * 鍙戦�佸埌鏈熸彁閱掔珯鍐呬俊锛堟噿瑙﹀彂锛屽箓绛夛級
+     *
+     * 鍦ㄨ閲忓櫒鍏峰垎椤垫煡璇㈡椂瑙﹀彂锛氬厛鎶㈠綋澶╂壂鎻忛棬闂╋紝鍐嶆煡鍒版湡鍣ㄥ叿锛屾寜鍣ㄥ叿+鍒版湡鏃ュ幓閲嶅悗閫愬彴鍙戠珯鍐呬俊銆�
+     */
+    void sendExpireRemind();
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java
new file mode 100644
index 0000000..2c8416c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java
@@ -0,0 +1,104 @@
+package cn.iocoder.yudao.module.mes.service.dv.metering;
+
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.metering.MesDvMeteringMapper;
+import cn.iocoder.yudao.module.mes.dal.redis.dv.metering.MesDvMeteringRemindRedisDAO;
+import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
+import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Service;
+
+import java.time.LocalDate;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿鍒版湡鎻愰啋 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Slf4j
+@Service
+public class MesDvMeteringRemindServiceImpl implements MesDvMeteringRemindService {
+
+    /**
+     * 鎻愰啋澶╂暟閰嶇疆 KEY锛坕nfra_config锛�
+     */
+    private static final String REMIND_DAYS_CONFIG_KEY = "mes.dv-metering.remind-days";
+    /**
+     * 鎻愰啋澶╂暟榛樿鍊�
+     */
+    private static final int DEFAULT_REMIND_DAYS = 30;
+    /**
+     * 鍒版湡鎻愰啋绔欏唴淇℃ā鏉跨紪鍙�
+     */
+    private static final String REMIND_TEMPLATE_CODE = "mes_metering_expire_remind";
+
+    @Resource
+    private MesDvMeteringMapper meteringMapper;
+    @Resource
+    private MesDvMeteringRemindRedisDAO meteringRemindRedisDAO;
+    @Resource
+    private ConfigApi configApi;
+    @Resource
+    private NotifyMessageSendApi notifyMessageSendApi;
+
+    @Override
+    public void sendExpireRemind() {
+        try {
+            // 1. 鎶㈠綋澶╂壂鎻忛棬闂╋紝閬垮厤澶氬疄渚�/澶氭骞跺彂閲嶅鎵弿
+            LocalDate today = LocalDate.now();
+            if (!meteringRemindRedisDAO.tryAcquireScanLock(today)) {
+                return;
+            }
+            // 2. 璇诲彇鎻愰啋澶╂暟锛堥粯璁� 30锛�
+            int remindDays = getRemindDays();
+            // 3. 鏌ヨ [today, today + remindDays] 鍒版湡鐨勫櫒鍏�
+            List<MesDvMeteringDO> list = meteringMapper.selectListByNextCheckDateBetween(
+                    today, today.plusDays(remindDays));
+            // 4. 閫愬彴鍙戦�佺珯鍐呬俊锛堟寜鍣ㄥ叿 + 鍒版湡鏃ュ幓閲嶏級
+            for (MesDvMeteringDO metering : list) {
+                if (metering.getChargeUserId() == null || metering.getNextCheckDate() == null) {
+                    continue;
+                }
+                if (!meteringRemindRedisDAO.tryMarkReminded(metering.getId(), metering.getNextCheckDate())) {
+                    continue;
+                }
+                sendRemindMessage(metering);
+            }
+        } catch (Exception ex) {
+            // best-effort锛氭彁閱掑け璐ヤ笉褰卞搷鍒楄〃鏌ヨ
+            log.error("[sendExpireRemind][鍙戦�佽閲忓櫒鍏峰埌鏈熸彁閱掑け璐", ex);
+        }
+    }
+
+    private int getRemindDays() {
+        String value = configApi.getConfigValueByKey(REMIND_DAYS_CONFIG_KEY);
+        if (StrUtil.isBlank(value)) {
+            return DEFAULT_REMIND_DAYS;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            log.warn("[getRemindDays][閰嶇疆 {} 鐨勫�� {} 闈炴硶锛屼娇鐢ㄩ粯璁ゅ�� {}]", REMIND_DAYS_CONFIG_KEY, value, DEFAULT_REMIND_DAYS);
+            return DEFAULT_REMIND_DAYS;
+        }
+    }
+
+    private void sendRemindMessage(MesDvMeteringDO metering) {
+        Map<String, Object> params = new HashMap<>();
+        params.put("meteringCode", metering.getCode());
+        params.put("meteringName", metering.getName());
+        params.put("nextCheckDate", metering.getNextCheckDate().toString());
+        NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
+                .setUserId(metering.getChargeUserId())
+                .setTemplateCode(REMIND_TEMPLATE_CODE)
+                .setTemplateParams(params);
+        notifyMessageSendApi.sendSingleMessageToAdmin(reqDTO);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java
new file mode 100644
index 0000000..6104c6e
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java
@@ -0,0 +1,109 @@
+package cn.iocoder.yudao.module.mes.service.dv.metering;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import jakarta.validation.Valid;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesDvMeteringService {
+
+    /**
+     * 鍒涘缓璁¢噺鍣ㄥ叿
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createMetering(@Valid MesDvMeteringSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊璁¢噺鍣ㄥ叿
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateMetering(@Valid MesDvMeteringSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎璁¢噺鍣ㄥ叿
+     *
+     * @param id 缂栧彿
+     */
+    void deleteMetering(Long id);
+
+    /**
+     * 鑾峰緱璁¢噺鍣ㄥ叿
+     *
+     * @param id 缂栧彿
+     * @return 璁¢噺鍣ㄥ叿
+     */
+    MesDvMeteringDO getMetering(Long id);
+
+    /**
+     * 鑾峰緱璁¢噺鍣ㄥ叿鍒嗛〉
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 璁¢噺鍣ㄥ叿鍒嗛〉
+     */
+    PageResult<MesDvMeteringDO> getMeteringPage(MesDvMeteringPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱璁¢噺鍣ㄥ叿鍒楄〃
+     *
+     * @return 璁¢噺鍣ㄥ叿鍒楄〃
+     */
+    List<MesDvMeteringDO> getMeteringList();
+
+    /**
+     * 鑾峰緱璁¢噺鍣ㄥ叿鍒楄〃
+     *
+     * @param ids 缂栧彿鏁扮粍
+     * @return 璁¢噺鍣ㄥ叿鍒楄〃
+     */
+    List<MesDvMeteringDO> getMeteringList(Collection<Long> ids);
+
+    /**
+     * 鑾峰緱璁¢噺鍣ㄥ叿 Map
+     *
+     * @param ids 缂栧彿鏁扮粍
+     * @return 璁¢噺鍣ㄥ叿 Map
+     */
+    default Map<Long, MesDvMeteringDO> getMeteringMap(Collection<Long> ids) {
+        return convertMap(getMeteringList(ids), MesDvMeteringDO::getId);
+    }
+
+    /**
+     * 鏍¢獙璁¢噺鍣ㄥ叿瀛樺湪
+     *
+     * @param id 缂栧彿
+     */
+    void validateMeteringExists(Long id);
+
+    /**
+     * 鑾峰緱鍒版湡鎻愰啋澶╂暟锛堥粯璁� 30锛�
+     *
+     * @return 鎻愰啋澶╂暟
+     */
+    int getRemindDays();
+
+    /**
+     * 鍥炲啓璁¢噺鍣ㄥ叿鐨勬渶杩戞娴嬩俊鎭紙妫�娴嬭褰曟柊澧�/淇敼/鍒犻櫎鍚庤皟鐢級
+     *
+     * @param meteringId 璁¢噺鍣ㄥ叿缂栧彿
+     * @param lastCheckDate 鏈�杩戞娴嬫棩鏈燂紙鏃犲垯缃┖锛�
+     * @param nextCheckDate 涓嬫妫�娴嬫棩鏈燂紙鏃犲垯缃┖锛�
+     */
+    void updateMeteringCheckInfo(Long meteringId, LocalDate lastCheckDate, LocalDate nextCheckDate);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
new file mode 100644
index 0000000..6c5ccbe
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
@@ -0,0 +1,181 @@
+package cn.iocoder.yudao.module.mes.service.dv.metering;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.ObjUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.infra.api.config.ConfigApi;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.metering.MesDvMeteringMapper;
+import cn.iocoder.yudao.module.mes.service.dv.meteringcheck.MesDvMeteringCheckService;
+import cn.hutool.core.util.StrUtil;
+import jakarta.annotation.Resource;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.time.LocalDate;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_CODE_DUPLICATE;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_HAS_CHECK_RECORD;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_NOT_EXISTS;
+
+/**
+ * MES 璁¢噺鍣ㄥ叿 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesDvMeteringServiceImpl implements MesDvMeteringService {
+
+    /**
+     * 鎻愰啋澶╂暟閰嶇疆 KEY锛坕nfra_config锛�
+     */
+    private static final String REMIND_DAYS_CONFIG_KEY = "mes.dv-metering.remind-days";
+    /**
+     * 鎻愰啋澶╂暟榛樿鍊�
+     */
+    private static final int DEFAULT_REMIND_DAYS = 30;
+
+    @Resource
+    private MesDvMeteringMapper meteringMapper;
+    @Resource
+    @Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶄笌璁¢噺妫�娴嬭褰� Service 寰幆渚濊禆
+    private MesDvMeteringCheckService meteringCheckService;
+    @Resource
+    private MesDvMeteringRemindService meteringRemindService;
+    @Resource
+    private ConfigApi configApi;
+
+    @Override
+    public Long createMetering(MesDvMeteringSaveReqVO createReqVO) {
+        // 鏍¢獙缂栫爜鍞竴
+        validateMeteringCodeUnique(null, createReqVO.getCode());
+
+        // 鎻掑叆
+        MesDvMeteringDO metering = BeanUtils.toBean(createReqVO, MesDvMeteringDO.class);
+        meteringMapper.insert(metering);
+        return metering.getId();
+    }
+
+    @Override
+    public void updateMetering(MesDvMeteringSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateMeteringExists(updateReqVO.getId());
+        // 鏍¢獙缂栫爜鍞竴
+        validateMeteringCodeUnique(updateReqVO.getId(), updateReqVO.getCode());
+
+        // 鏇存柊
+        MesDvMeteringDO updateObj = BeanUtils.toBean(updateReqVO, MesDvMeteringDO.class);
+        meteringMapper.updateById(updateObj);
+    }
+
+    @Override
+    public void deleteMetering(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateMeteringExists(id);
+        // 鏍¢獙鏃犲叧鑱旀娴嬭褰�
+        if (meteringCheckService.getMeteringCheckCountByMeteringId(id) > 0) {
+            throw exception(DV_METERING_HAS_CHECK_RECORD);
+        }
+
+        // 鍒犻櫎
+        meteringMapper.deleteById(id);
+    }
+
+    @Override
+    public void validateMeteringExists(Long id) {
+        if (meteringMapper.selectById(id) == null) {
+            throw exception(DV_METERING_NOT_EXISTS);
+        }
+    }
+
+    private void validateMeteringCodeUnique(Long id, String code) {
+        if (StrUtil.isBlank(code)) {
+            return;
+        }
+        MesDvMeteringDO metering = meteringMapper.selectByCode(code);
+        if (metering == null) {
+            return;
+        }
+        if (ObjUtil.notEqual(metering.getId(), id)) {
+            throw exception(DV_METERING_CODE_DUPLICATE);
+        }
+    }
+
+    @Override
+    public MesDvMeteringDO getMetering(Long id) {
+        return meteringMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesDvMeteringDO> getMeteringPage(MesDvMeteringPageReqVO pageReqVO) {
+        // 1. 鍒版湡鐘舵�佽浆鎹负鏃ユ湡鑼冨洿锛岃蛋 DB 杩囨护
+        Integer expireStatus = pageReqVO.getExpireStatus();
+        if (expireStatus != null) {
+            LocalDate today = LocalDate.now();
+            int remindDays = getRemindDays();
+            switch (expireStatus) {
+                case 1 -> { // 宸茶繃鏈燂細鍒版湡鏃� < 浠婂ぉ
+                    pageReqVO.setNextCheckDateEnd(today.minusDays(1));
+                }
+                case 2 -> { // 鍗冲皢鍒版湡锛氬埌鏈熸棩鍦� [浠婂ぉ, 浠婂ぉ+鎻愰啋澶╂暟]
+                    pageReqVO.setNextCheckDateBegin(today);
+                    pageReqVO.setNextCheckDateEnd(today.plusDays(remindDays));
+                }
+                case 3 -> { // 姝e父锛氬埌鏈熸棩 > 浠婂ぉ+鎻愰啋澶╂暟
+                    pageReqVO.setNextCheckDateBegin(today.plusDays(remindDays + 1));
+                }
+                default -> {
+                }
+            }
+        }
+        // 2. 鏌ヨ
+        PageResult<MesDvMeteringDO> pageResult = meteringMapper.selectPage(pageReqVO);
+        // 3. 鎳掕Е鍙戝埌鏈熸彁閱掔珯鍐呬俊锛坆est-effort锛屼笉褰卞搷鏌ヨ锛�
+        meteringRemindService.sendExpireRemind();
+        return pageResult;
+    }
+
+    @Override
+    public int getRemindDays() {
+        String value = configApi.getConfigValueByKey(REMIND_DAYS_CONFIG_KEY);
+        if (StrUtil.isBlank(value)) {
+            return DEFAULT_REMIND_DAYS;
+        }
+        try {
+            return Integer.parseInt(value.trim());
+        } catch (NumberFormatException ex) {
+            return DEFAULT_REMIND_DAYS;
+        }
+    }
+
+    @Override
+    public void updateMeteringCheckInfo(Long meteringId, LocalDate lastCheckDate, LocalDate nextCheckDate) {
+        meteringMapper.updateById(new MesDvMeteringDO()
+                .setId(meteringId)
+                .setLastCheckDate(lastCheckDate)
+                .setNextCheckDate(nextCheckDate));
+    }
+
+    @Override
+    public List<MesDvMeteringDO> getMeteringList() {
+        return meteringMapper.selectList();
+    }
+
+    @Override
+    public List<MesDvMeteringDO> getMeteringList(Collection<Long> ids) {
+        if (CollUtil.isEmpty(ids)) {
+            return Collections.emptyList();
+        }
+        return meteringMapper.selectByIds(ids);
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckService.java
new file mode 100644
index 0000000..33f52c3
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckService.java
@@ -0,0 +1,69 @@
+package cn.iocoder.yudao.module.mes.service.dv.meteringcheck;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.meteringcheck.MesDvMeteringCheckDO;
+import jakarta.validation.Valid;
+
+/**
+ * MES 璁¢噺妫�娴嬭褰� Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface MesDvMeteringCheckService {
+
+    /**
+     * 鍒涘缓璁¢噺妫�娴嬭褰�
+     *
+     * @param createReqVO 鍒涘缓淇℃伅
+     * @return 缂栧彿
+     */
+    Long createMeteringCheck(@Valid MesDvMeteringCheckSaveReqVO createReqVO);
+
+    /**
+     * 鏇存柊璁¢噺妫�娴嬭褰�
+     *
+     * @param updateReqVO 鏇存柊淇℃伅
+     */
+    void updateMeteringCheck(@Valid MesDvMeteringCheckSaveReqVO updateReqVO);
+
+    /**
+     * 鍒犻櫎璁¢噺妫�娴嬭褰�
+     *
+     * @param id 缂栧彿
+     */
+    void deleteMeteringCheck(Long id);
+
+    /**
+     * 鑾峰緱璁¢噺妫�娴嬭褰�
+     *
+     * @param id 缂栧彿
+     * @return 璁¢噺妫�娴嬭褰�
+     */
+    MesDvMeteringCheckDO getMeteringCheck(Long id);
+
+    /**
+     * 鑾峰緱璁¢噺妫�娴嬭褰曞垎椤�
+     *
+     * @param pageReqVO 鍒嗛〉鏌ヨ
+     * @return 璁¢噺妫�娴嬭褰曞垎椤�
+     */
+    PageResult<MesDvMeteringCheckDO> getMeteringCheckPage(MesDvMeteringCheckPageReqVO pageReqVO);
+
+    /**
+     * 鑾峰緱鎸囧畾璁¢噺鍣ㄥ叿涓嬬殑妫�娴嬭褰曟暟閲�
+     *
+     * @param meteringId 璁¢噺鍣ㄥ叿缂栧彿
+     * @return 妫�娴嬭褰曟暟閲�
+     */
+    Long getMeteringCheckCountByMeteringId(Long meteringId);
+
+    /**
+     * 鏍¢獙璁¢噺妫�娴嬭褰曞瓨鍦�
+     *
+     * @param id 缂栧彿
+     */
+    void validateMeteringCheckExists(Long id);
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckServiceImpl.java
new file mode 100644
index 0000000..b1e0f3c
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/meteringcheck/MesDvMeteringCheckServiceImpl.java
@@ -0,0 +1,165 @@
+package cn.iocoder.yudao.module.mes.service.dv.meteringcheck;
+
+import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckPageReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.meteringcheck.vo.MesDvMeteringCheckSaveReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.dv.meteringcheck.MesDvMeteringCheckDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.dv.meteringcheck.MesDvMeteringCheckMapper;
+import cn.iocoder.yudao.module.mes.service.dv.metering.MesDvMeteringService;
+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.time.LocalDate;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_CHECK_CODE_DUPLICATE;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_CHECK_NOT_EXISTS;
+import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.DV_METERING_NOT_EXISTS;
+
+/**
+ * MES 璁¢噺妫�娴嬭褰� Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class MesDvMeteringCheckServiceImpl implements MesDvMeteringCheckService {
+
+    @Resource
+    private MesDvMeteringCheckMapper meteringCheckMapper;
+    @Resource
+    @Lazy // 寤惰繜鍔犺浇锛岄伩鍏嶄笌璁¢噺鍣ㄥ叿 Service 寰幆渚濊禆
+    private MesDvMeteringService meteringService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long createMeteringCheck(MesDvMeteringCheckSaveReqVO createReqVO) {
+        // 鏍¢獙璁¢噺鍣ㄥ叿瀛樺湪
+        MesDvMeteringDO metering = validateMeteringExists(createReqVO.getMeteringId());
+        // 鏍¢獙妫�娴嬬紪鍙峰敮涓�
+        validateMeteringCheckCodeUnique(null, createReqVO.getCheckCode());
+
+        // 璁$畻涓嬫妫�娴嬫棩鏈燂紙涓虹┖鍒欐寜鏍″噯鍛ㄦ湡鑷姩璁$畻锛�
+        LocalDate nextCheckDate = computeNextCheckDate(createReqVO.getCheckDate(),
+                createReqVO.getNextCheckDate(), metering);
+        MesDvMeteringCheckDO check = BeanUtils.toBean(createReqVO, MesDvMeteringCheckDO.class);
+        check.setNextCheckDate(nextCheckDate);
+        meteringCheckMapper.insert(check);
+
+        // 鍥炲啓璁¢噺鍣ㄥ叿鐨勬渶杩戞娴嬩俊鎭�
+        refreshMeteringCheckInfo(createReqVO.getMeteringId());
+        return check.getId();
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void updateMeteringCheck(MesDvMeteringCheckSaveReqVO updateReqVO) {
+        // 鏍¢獙瀛樺湪
+        validateMeteringCheckExists(updateReqVO.getId());
+        // 鏍¢獙璁¢噺鍣ㄥ叿瀛樺湪
+        MesDvMeteringDO metering = validateMeteringExists(updateReqVO.getMeteringId());
+        // 鏍¢獙妫�娴嬬紪鍙峰敮涓�
+        validateMeteringCheckCodeUnique(updateReqVO.getId(), updateReqVO.getCheckCode());
+
+        // 璁$畻涓嬫妫�娴嬫棩鏈燂紙涓虹┖鍒欐寜鏍″噯鍛ㄦ湡鑷姩璁$畻锛�
+        LocalDate nextCheckDate = computeNextCheckDate(updateReqVO.getCheckDate(),
+                updateReqVO.getNextCheckDate(), metering);
+        MesDvMeteringCheckDO updateObj = BeanUtils.toBean(updateReqVO, MesDvMeteringCheckDO.class);
+        updateObj.setNextCheckDate(nextCheckDate);
+        meteringCheckMapper.updateById(updateObj);
+
+        // 鍥炲啓璁¢噺鍣ㄥ叿鐨勬渶杩戞娴嬩俊鎭�
+        refreshMeteringCheckInfo(updateReqVO.getMeteringId());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void deleteMeteringCheck(Long id) {
+        // 鏍¢獙瀛樺湪
+        validateMeteringCheckExists(id);
+        MesDvMeteringCheckDO check = meteringCheckMapper.selectById(id);
+
+        // 鍒犻櫎
+        meteringCheckMapper.deleteById(id);
+
+        // 鍥炲啓璁¢噺鍣ㄥ叿鐨勬渶杩戞娴嬩俊鎭�
+        refreshMeteringCheckInfo(check.getMeteringId());
+    }
+
+    @Override
+    public MesDvMeteringCheckDO getMeteringCheck(Long id) {
+        return meteringCheckMapper.selectById(id);
+    }
+
+    @Override
+    public PageResult<MesDvMeteringCheckDO> getMeteringCheckPage(MesDvMeteringCheckPageReqVO pageReqVO) {
+        return meteringCheckMapper.selectPage(pageReqVO);
+    }
+
+    @Override
+    public Long getMeteringCheckCountByMeteringId(Long meteringId) {
+        return meteringCheckMapper.selectCountByMeteringId(meteringId);
+    }
+
+    @Override
+    public void validateMeteringCheckExists(Long id) {
+        if (meteringCheckMapper.selectById(id) == null) {
+            throw exception(DV_METERING_CHECK_NOT_EXISTS);
+        }
+    }
+
+    private MesDvMeteringDO validateMeteringExists(Long meteringId) {
+        MesDvMeteringDO metering = meteringService.getMetering(meteringId);
+        if (metering == null) {
+            throw exception(DV_METERING_NOT_EXISTS);
+        }
+        return metering;
+    }
+
+    private void validateMeteringCheckCodeUnique(Long id, String checkCode) {
+        if (StrUtil.isBlank(checkCode)) {
+            return;
+        }
+        MesDvMeteringCheckDO check = meteringCheckMapper.selectByCheckCode(checkCode);
+        if (check == null) {
+            return;
+        }
+        if (ObjUtil.notEqual(check.getId(), id)) {
+            throw exception(DV_METERING_CHECK_CODE_DUPLICATE);
+        }
+    }
+
+    /**
+     * 璁$畻涓嬫妫�娴嬫棩鏈燂細鎵嬪姩鎸囧畾浼樺厛锛屽惁鍒欐寜 妫�娴嬫棩鏈� + 鏍″噯鍛ㄦ湡锛堟湀锛夎嚜鍔ㄨ绠�
+     */
+    private LocalDate computeNextCheckDate(LocalDate checkDate, LocalDate nextCheckDate, MesDvMeteringDO metering) {
+        if (nextCheckDate != null) {
+            return nextCheckDate;
+        }
+        if (checkDate != null && metering.getCheckCycleCount() != null) {
+            return checkDate.plusMonths(metering.getCheckCycleCount());
+        }
+        return nextCheckDate;
+    }
+
+    /**
+     * 鎸�"鏈�鏂颁竴鏉℃娴嬭褰�"鏁翠綋閲嶇畻鍥炲啓璁¢噺鍣ㄥ叿锛屾纭鐩栫紪杈戦潪鏈�鏂拌褰�/鍒犻櫎涓棿璁板綍绛夎竟鐣�
+     */
+    private void refreshMeteringCheckInfo(Long meteringId) {
+        MesDvMeteringCheckDO latest = meteringCheckMapper.selectLatestByMeteringId(meteringId);
+        if (latest == null) {
+            meteringService.updateMeteringCheckInfo(meteringId, null, null);
+        } else {
+            meteringService.updateMeteringCheckInfo(meteringId,
+                    latest.getCheckDate(), latest.getNextCheckDate());
+        }
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
index 698e9ce..5a46e79 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemServiceImpl.java
@@ -84,7 +84,9 @@
 
         // 鎻掑叆
         MesMdItemDO item = BeanUtils.toBean(createReqVO, MesMdItemDO.class);
-        item.setStatus(CommonStatusEnum.DISABLE.getStatus()); // 榛樿绂佺敤銆備俊鎭畬鎴愬悗锛屽啀鍚敤
+        // 鐘舵�佸彧鑳界敱鍩虹鏁版嵁锛圡DM锛夋帶鍒讹細MDM 鍚屾鏃舵部鐢ㄥ叾鐘舵�侊紱MES 鏈湴鏂板榛樿绂佺敤
+        item.setStatus(createReqVO.getMdmItemId() != null && createReqVO.getStatus() != null
+                ? createReqVO.getStatus() : CommonStatusEnum.DISABLE.getStatus());
         clearStockIfNotSafe(item); // 濡傛灉鏈惎鐢ㄥ畨鍏ㄥ簱瀛橈紝娓呴浂搴撳瓨涓婁笅闄�
         itemMapper.insert(item);
 
@@ -111,6 +113,10 @@
         // 鏇存柊
         MesMdItemDO updateObj = BeanUtils.toBean(updateReqVO, MesMdItemDO.class);
         clearStockIfNotSafe(updateObj); // 濡傛灉鏈惎鐢ㄥ畨鍏ㄥ簱瀛橈紝娓呴浂搴撳瓨涓婁笅闄�
+        // 鐘舵�佸彧鑳界敱鍩虹鏁版嵁锛圡DM锛夋帶鍒讹紝MES 鏈湴鏇存柊涓嶅厑璁镐慨鏀圭姸鎬�
+        if (updateReqVO.getMdmItemId() == null) {
+            updateObj.setStatus(null);
+        }
         itemMapper.updateById(updateObj);
 
         // 鍚屾鍒� MDM锛堝嬀閫� syncMdm 鏃讹級
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java
index 2e1bb45..54deaeb 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectService.java
+++ b/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 id                  缂栧彿
+     * @param processDefinitionKey 娴佺▼瀹氫箟 Key
      */
-    void auditProject(Long id);
+    void submitProjectApproval(Long id, String processDefinitionKey);
+
+    /**
+     * 鑾峰緱璁捐椤圭洰瀹℃壒娴佺▼鍒楄〃
+     *
+     * @return 娴佺▼鍒楄〃锛坕d/name/version/key锛�
+     */
+    List<Map<String, Object>> getProjectApproveProcessDefinitionList();
+
+    /**
+     * 鏇存柊璁捐椤圭洰瀹℃壒鐘舵�侊紙BPM 鐩戝惉鍣ㄥ洖璋冿級
+     *
+     * @param id       缂栧彿
+     * @param bpmResult BPM 瀹℃壒缁撴灉
+     */
+    void updateProjectAuditStatus(Long id, Integer bpmResult);
 
     /**
      * 褰掓。璁捐椤圭洰锛堝緟褰掓。鈫掑凡褰掓。锛�
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
index 12a637d..6648c60 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/MesPdProjectServiceImpl.java
+++ b/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) {
         // 鏍¢獙瀛樺湪
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/project/listener/MesPdProjectStatusListener.java
new file mode 100644
index 0000000..31402d1
--- /dev/null
+++ b/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瀹℃壒浜嬩欢锛宲rojectId={}, status={}", projectId, event.getStatus());
+        projectService.updateProjectAuditStatus(projectId, event.getStatus());
+    }
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
index 66048dd..cf58d84 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchService.java
+++ b/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);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
index d218f8c..ea88533 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
+++ b/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());
+    }
+
 }
diff --git a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml b/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
index 22fba71..0eda416 100644
--- a/yudao-module-mes/src/main/resources/mapper/wm/batch/MesWmBatchMapper.xml
+++ b/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=浜у嚭鍗昳d) 鈫� 鎵规
     -->
     <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=浜у嚭鍗昳d) 鈫� 浜у嚭鍗� 鈫� 鎶ュ伐 鈫� 娑堣�楀崟(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>
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java
index 180780d..47f69b1 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplyRespVO.java
@@ -40,6 +40,12 @@
     @Schema(description = "鍏徃鍦板潃", example = "涓婃捣甯傛郸涓滄柊鍖篨X璺疿X鍙�")
     private String address;
 
+    @Schema(description = "寮�鎴烽摱琛�", example = "涓浗宸ュ晢閾惰涓婃捣鍒嗚")
+    private String bankName;
+
+    @Schema(description = "閾惰璐﹀彿", example = "622202xxxxxxxxxxxx")
+    private String bankAccount;
+
     @Schema(description = "鑱旂郴浜�", example = "寮犱笁")
     private String contactName;
 
@@ -49,6 +55,9 @@
     @Schema(description = "閭", example = "zhangsan@example.com")
     private String contactEmail;
 
+    @Schema(description = "澶囨敞", example = "浼樺厛渚涜揣")
+    private String remark;
+
     @Schema(description = "鐢宠浜篒D", example = "1")
     private Long applyUserId;
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java
index 3148832..4f63577 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/apply/vo/SrmSupplierApplySaveReqVO.java
@@ -36,6 +36,12 @@
     @Schema(description = "鍏徃鍦板潃", example = "涓婃捣甯傛郸涓滄柊鍖篨X璺疿X鍙�")
     private String address;
 
+    @Schema(description = "寮�鎴烽摱琛�", example = "涓浗宸ュ晢閾惰涓婃捣鍒嗚")
+    private String bankName;
+
+    @Schema(description = "閾惰璐﹀彿", example = "622202xxxxxxxxxxxx")
+    private String bankAccount;
+
     @Schema(description = "鑱旂郴浜�", example = "寮犱笁")
     private String contactName;
 
@@ -45,4 +51,7 @@
     @Schema(description = "閭", example = "zhangsan@example.com")
     private String contactEmail;
 
+    @Schema(description = "澶囨敞", example = "浼樺厛渚涜揣")
+    private String remark;
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
index 003ed7e..40d0a03 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/SrmSupplierCertificateController.java
@@ -6,7 +6,9 @@
 import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.*;
 import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
 import cn.iocoder.yudao.module.srm.service.certificate.SrmSupplierCertificateService;
+import cn.iocoder.yudao.module.srm.service.certificate.SrmSupplierCertificateServiceImpl;
 import cn.iocoder.yudao.module.srm.service.supplier.SrmSupplierService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -34,6 +36,9 @@
 
     @Resource
     private SrmSupplierService supplierService;
+
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓璧勮川璇佷功")
@@ -66,6 +71,7 @@
         var pageResult = certificateService.getCertificatePage(pageReqVO);
         var respList = BeanUtils.toBean(pageResult.getList(), SrmSupplierCertificateRespVO.class);
         enrichSupplierNames(respList);
+        enrichAttachments(respList);
         return success(new PageResult<>(respList, pageResult.getTotal()));
     }
 
@@ -75,6 +81,7 @@
     public CommonResult<List<SrmSupplierCertificateRespVO>> getListBySupplier(@RequestParam("supplierId") Long supplierId) {
         var list = BeanUtils.toBean(certificateService.getCertificateListBySupplierId(supplierId), SrmSupplierCertificateRespVO.class);
         enrichSupplierNames(list);
+        enrichAttachments(list);
         return success(list);
     }
 
@@ -93,6 +100,7 @@
     public CommonResult<SrmSupplierCertificateRespVO> getCertificate(@RequestParam("id") Long id) {
         var resp = BeanUtils.toBean(certificateService.getCertificate(id), SrmSupplierCertificateRespVO.class);
         enrichSupplierName(resp);
+        enrichAttachments(List.of(resp));
         return success(resp);
     }
 
@@ -118,4 +126,10 @@
         }
     }
 
+    private void enrichAttachments(List<SrmSupplierCertificateRespVO> list) {
+        if (list == null || list.isEmpty()) return;
+        list.forEach(vo -> vo.setAttachmentList(storageAttachmentApi.listAttachments(
+                SrmSupplierCertificateServiceImpl.CERTIFICATE_RECORD_TYPE, vo.getId())));
+    }
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java
index 672a969..0cb8443 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateRespVO.java
@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.srm.controller.admin.certificate.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - SRM 渚涘簲鍟嗚祫璐ㄨ瘉涔� Response VO")
 @Data
@@ -52,4 +54,7 @@
     @Schema(description = "鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
 
+    @Schema(description = "璇佷功闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java
index 64c493e..3938430c 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificateSaveReqVO.java
@@ -6,6 +6,7 @@
 import lombok.Data;
 
 import java.time.LocalDate;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - SRM 渚涘簲鍟嗚祫璐ㄨ瘉涔﹀垱寤�/鏇存柊 Request VO")
 @Data
@@ -47,4 +48,7 @@
     @Schema(description = "澶囨敞", example = "骞村害璧勮川瀹℃牳")
     private String remark;
 
+    @Schema(description = "璇佷功闄勪欢鏂囦欢ID鍒楄〃")
+    private List<Long> blobIds;
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
index e183de0..c0b71d8 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/supplier/SrmSupplierController.java
@@ -80,6 +80,13 @@
         return success(BeanUtils.toBean(list, SrmSupplierRespVO.class));
     }
 
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰彇渚涘簲鍟嗘。妗堝垪琛紙涓嶈繃婊ゅ噯鍏ョ姸鎬侊級")
+    public CommonResult<List<SrmSupplierRespVO>> getSupplierAllList() {
+        List<SrmSupplierDO> list = supplierService.getSupplierAllList();
+        return success(BeanUtils.toBean(list, SrmSupplierRespVO.class));
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "瀵煎嚭渚涘簲鍟� Excel")
     @PreAuthorize("@ss.hasPermission('srm:supplier:export')")
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java
index 8126210..abed6e7 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/SrmTenderProjectController.java
@@ -5,6 +5,8 @@
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.*;
 import cn.iocoder.yudao.module.srm.service.tender.SrmTenderProjectService;
+import cn.iocoder.yudao.module.srm.service.tender.SrmTenderProjectServiceImpl;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -26,6 +28,9 @@
 
     @Resource
     private SrmTenderProjectService tenderProjectService;
+
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓鎷涙爣椤圭洰")
@@ -54,14 +59,27 @@
     @Operation(summary = "鑾峰彇鎷涙爣椤圭洰璇︽儏")
     @PreAuthorize("@ss.hasPermission('srm:tender-project:query')")
     public CommonResult<SrmTenderProjectRespVO> getTenderProject(@RequestParam("id") Long id) {
-        return success(BeanUtils.toBean(tenderProjectService.getTenderProject(id), SrmTenderProjectRespVO.class));
+        var resp = BeanUtils.toBean(tenderProjectService.getTenderProject(id), SrmTenderProjectRespVO.class);
+        enrichAttachments(List.of(resp));
+        return success(resp);
     }
 
     @GetMapping("/page")
     @Operation(summary = "鑾峰彇鎷涙爣椤圭洰鍒嗛〉")
     @PreAuthorize("@ss.hasPermission('srm:tender-project:query')")
     public CommonResult<PageResult<SrmTenderProjectRespVO>> getTenderProjectPage(@Valid SrmTenderProjectPageReqVO pageReqVO) {
-        return success(BeanUtils.toBean(tenderProjectService.getTenderProjectPage(pageReqVO), SrmTenderProjectRespVO.class));
+        var pageResult = tenderProjectService.getTenderProjectPage(pageReqVO);
+        var respList = BeanUtils.toBean(pageResult.getList(), SrmTenderProjectRespVO.class);
+        enrichAttachments(respList);
+        return success(new PageResult<>(respList, pageResult.getTotal()));
+    }
+
+    // ========== 鍏宠仈鏁版嵁濉厖 ==========
+
+    private void enrichAttachments(List<SrmTenderProjectRespVO> list) {
+        if (list == null || list.isEmpty()) return;
+        list.forEach(vo -> vo.setAttachmentList(storageAttachmentApi.listAttachments(
+                SrmTenderProjectServiceImpl.TENDER_PROJECT_RECORD_TYPE, vo.getId())));
     }
 
     @PutMapping("/publish")
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java
index 0b8aa99..aa7203d 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectPartialUpdateReqVO.java
@@ -4,6 +4,8 @@
 import jakarta.validation.constraints.NotNull;
 import lombok.Data;
 
+import java.util.List;
+
 @Schema(description = "绠$悊鍚庡彴 - SRM 鎷涙爣椤圭洰閮ㄥ垎鏇存柊 Request VO锛堝彂甯冧腑/鎶曟爣涓樁娈碉級")
 @Data
 public class SrmTenderProjectPartialUpdateReqVO {
@@ -18,4 +20,7 @@
     @Schema(description = "璧勮川瑕佹眰", example = "闇�鍏峰ISO9001璁よ瘉")
     private String qualificationRequirements;
 
+    @Schema(description = "璧勮川瑕佹眰闄勪欢鏂囦欢ID鍒楄〃")
+    private List<Long> blobIds;
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java
index f32cd6a..ccaebb8 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectRespVO.java
@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.module.srm.controller.admin.tender.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - SRM 鎷涙爣椤圭洰 Response VO")
 @Data
@@ -55,6 +57,9 @@
     @Schema(description = "璧勮川瑕佹眰", example = "闇�鍏峰ISO9001璁よ瘉")
     private String qualificationRequirements;
 
+    @Schema(description = "璧勮川瑕佹眰闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
+
     @Schema(description = "閲囪喘鐢宠ID", example = "1")
     private Long purchaseRequestId;
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java
index f0e2385..436e1d8 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/tender/vo/SrmTenderProjectSaveReqVO.java
@@ -7,6 +7,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - SRM 鎷涙爣椤圭洰鍒涘缓/鏇存柊 Request VO")
 @Data
@@ -60,6 +61,9 @@
     @Schema(description = "璧勮川瑕佹眰", example = "闇�鍏峰ISO9001璁よ瘉")
     private String qualificationRequirements;
 
+    @Schema(description = "璧勮川瑕佹眰闄勪欢鏂囦欢ID鍒楄〃")
+    private List<Long> blobIds;
+
     @Schema(description = "閲囪喘鐢宠ID", example = "1")
     private Long purchaseRequestId;
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java
index 0e8257b..8c0d57b 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/dataobject/SrmSupplierApplyDO.java
@@ -4,6 +4,7 @@
 import com.baomidou.mybatisplus.annotation.KeySequence;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
+import com.baomidou.mybatisplus.annotation.TableField;
 import lombok.*;
 
 import java.math.BigDecimal;
@@ -34,6 +35,8 @@
     private String businessLicenseNo;
     private String taxNo;
     private String address;
+    private String bankName;
+    private String bankAccount;
     private String contactName;
     private String contactMobile;
     private String contactEmail;
@@ -53,5 +56,6 @@
     private Long financeReviewerId;
     private LocalDateTime financeReviewTime;
     private String financeReviewOpinion;
+    private String remark;
 
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java
index 99bff78..5ab62d6 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/TenderStatusEnum.java
@@ -44,9 +44,9 @@
         return DRAFT.getCode().equals(status);
     }
 
-    /** PUBLISHED 鎴� BIDDING锛堝畬鏁寸増锛夛紝鎴� PRICE_COMPARING锛堢畝鏄撶増锛夊厑璁镐緵搴斿晢鎶曟爣 */
+    /** BIDDING锛堝畬鏁寸増锛夛紝鎴� PRICE_COMPARING锛堢畝鏄撶増锛夊厑璁镐緵搴斿晢鎶曟爣 */
     public static boolean canCreateBid(Integer status) {
-        return PUBLISHED.getCode().equals(status) || BIDDING.getCode().equals(status)
+        return BIDDING.getCode().equals(status)
                 || PRICE_COMPARING.getCode().equals(status);
     }
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java
index 30e75da..ba759ca 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyService.java
@@ -61,4 +61,11 @@
      */
     void approve(Long id);
 
+    /**
+     * 纭繚渚涘簲鍟嗗凡鍑嗗叆锛氭湭鍑嗗叆鍒欒嚜鍔ㄥ垱寤哄噯鍏ョ敵璇峰苟缃负宸查�氳繃
+     *
+     * @param supplierId 渚涘簲鍟嗙紪鍙�
+     */
+    void ensureSupplierApproved(Long supplierId);
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java
index c7a6914..9a1a9f3 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/apply/SrmSupplierApplyServiceImpl.java
@@ -145,6 +145,43 @@
 
     @Override
     @Transactional(rollbackFor = Exception.class)
+    public void ensureSupplierApproved(Long supplierId) {
+        if (supplierId == null) {
+            return;
+        }
+        // 鏍¢獙渚涘簲鍟嗗瓨鍦�
+        SrmSupplierDO supplier = supplierMapper.selectById(supplierId);
+        if (supplier == null) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
+        }
+        // 宸插噯鍏ュ垯鏃犻渶澶勭悊
+        if (supplierApplyMapper.existsApprovedBySupplierId(supplierId)) {
+            return;
+        }
+        // 鑷姩鍒涘缓鍑嗗叆鐢宠骞剁洿鎺ョ疆涓哄凡閫氳繃
+        SrmSupplierApplyDO apply = new SrmSupplierApplyDO();
+        apply.setApplyNo(generateApplyNo());
+        apply.setSupplierId(supplierId);
+        apply.setSupplierName(supplier.getName());
+        apply.setSupplierType(supplier.getType());
+        apply.setCompanyNature(supplier.getCompanyNature());
+        apply.setRegisteredCapital(supplier.getRegisteredCapital());
+        apply.setBusinessLicenseNo(supplier.getBusinessLicenseNo());
+        apply.setTaxNo(supplier.getTaxNo());
+        apply.setAddress(supplier.getAddress());
+        apply.setBankName(supplier.getBankName());
+        apply.setBankAccount(supplier.getBankAccount());
+        apply.setContactName(supplier.getContactName());
+        apply.setContactMobile(supplier.getContactMobile());
+        apply.setContactEmail(supplier.getContactEmail());
+        apply.setRemark("涓爣鐢熸垚閲囪喘璁㈠崟鏃惰嚜鍔ㄥ噯鍏ュ苟閫氳繃");
+        apply.setApplyStatus(SupplierApplyStatusEnum.APPROVED.getCode());
+        apply.setApplyTime(LocalDateTime.now());
+        supplierApplyMapper.insert(apply);
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
     public void approve(Long id) {
         SrmSupplierApplyDO apply = validateApplyExists(id);
         if (!SupplierApplyStatusEnum.APPROVED.getCode().equals(apply.getApplyStatus())) {
@@ -159,9 +196,12 @@
             supplier.setBusinessLicenseNo(apply.getBusinessLicenseNo());
             supplier.setTaxNo(apply.getTaxNo());
             supplier.setAddress(apply.getAddress());
+            supplier.setBankName(apply.getBankName());
+            supplier.setBankAccount(apply.getBankAccount());
             supplier.setContactName(apply.getContactName());
             supplier.setContactMobile(apply.getContactMobile());
             supplier.setContactEmail(apply.getContactEmail());
+            supplier.setRemark(apply.getRemark());
             supplier.setStatus(0);
             supplierMapper.insert(supplier);
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java
index 7dddd49..78132f6 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/certificate/SrmSupplierCertificateServiceImpl.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.srm.service.certificate;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.srm.controller.admin.certificate.vo.SrmSupplierCertificatePageReqVO;
@@ -8,8 +9,10 @@
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCertificateMapper;
 import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.time.LocalDate;
@@ -19,28 +22,47 @@
 @Validated
 public class SrmSupplierCertificateServiceImpl implements SrmSupplierCertificateService {
 
+    /**
+     * 璧勮川璇佷功闄勪欢鐨勪笟鍔¤褰曠被鍨嬶紝瀵瑰簲 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#SRM_SUPPLIER_CERTIFICATE}
+     */
+    public static final String CERTIFICATE_RECORD_TYPE = "srm_supplier_certificate";
+
     @Resource
     private SrmSupplierCertificateMapper certificateMapper;
 
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createCertificate(SrmSupplierCertificateSaveReqVO createReqVO) {
         SrmSupplierCertificateDO certificate = BeanUtils.toBean(createReqVO, SrmSupplierCertificateDO.class);
         updateStatus(certificate);
         certificateMapper.insert(certificate);
+        // 缁戝畾璇佷功闄勪欢
+        if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
+            storageAttachmentApi.bindAttachments("file", CERTIFICATE_RECORD_TYPE, certificate.getId(), createReqVO.getBlobIds());
+        }
         return certificate.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateCertificate(SrmSupplierCertificateSaveReqVO updateReqVO) {
         validateCertificateExists(updateReqVO.getId());
         SrmSupplierCertificateDO updateObj = BeanUtils.toBean(updateReqVO, SrmSupplierCertificateDO.class);
         updateStatus(updateObj);
         certificateMapper.updateById(updateObj);
+        // 澧為噺鏇存柊璇佷功闄勪欢
+        storageAttachmentApi.updateAttachments("file", CERTIFICATE_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteCertificate(Long id) {
         validateCertificateExists(id);
+        // 鍒犻櫎璇佷功闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord(CERTIFICATE_RECORD_TYPE, id);
         certificateMapper.deleteById(id);
     }
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
index c43333c..2b8783d 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierService.java
@@ -46,6 +46,11 @@
     List<SrmSupplierDO> getSupplierSimpleList();
 
     /**
+     * 鑾峰彇鍏ㄩ儴渚涘簲鍟嗘。妗堝垪琛紙涓嶈繃婊ゅ噯鍏ョ姸鎬侊紝鐢ㄤ簬鎶曟爣閫夋嫨渚涘簲鍟嗭級
+     */
+    List<SrmSupplierDO> getSupplierAllList();
+
+    /**
      * 鑾峰彇渚涘簲鍟嗗垪琛�
      */
     List<SrmSupplierDO> getSupplierList(Collection<Long> ids);
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
index 9e7b9be..c9d5858 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -3,17 +3,24 @@
 import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierSaveReqVO;
-import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
+import cn.iocoder.yudao.module.srm.dal.dataobject.*;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidAwardMapper;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmBidEvaluationMapper;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCategoryMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierContactMapper;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierCertificateMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
 import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
+import org.springframework.transaction.annotation.Transactional;
 
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 
@@ -34,6 +41,21 @@
 
     @Resource
     private SrmSupplierCategoryMapper supplierCategoryMapper;
+
+    @Resource
+    private SrmSupplierApplyMapper supplierApplyMapper;
+
+    @Resource
+    private SrmSupplierCertificateMapper supplierCertificateMapper;
+
+    @Resource
+    private SrmTenderBidMapper tenderBidMapper;
+
+    @Resource
+    private SrmBidEvaluationMapper bidEvaluationMapper;
+
+    @Resource
+    private SrmBidAwardMapper bidAwardMapper;
 
     @Override
     public Long createSupplier(SrmSupplierSaveReqVO createReqVO) {
@@ -57,10 +79,13 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteSupplier(Long id) {
         // 鏍¢獙瀛樺湪鎬�
         validateSupplierExists(id);
-        // 鍒犻櫎鍏宠仈鏁版嵁
+        // 鍏宠仈鏁版嵁瀛樺湪鏃讹紝涓嶅厑璁稿垹闄�
+        validateNoReference(id);
+        // 鍙洿鎺ユ竻鐞嗙殑鍏宠仈鏁版嵁
         supplierContactMapper.deleteBySupplierId(id);
         supplierCategoryMapper.deleteBySupplierId(id);
         // 閫昏緫鍒犻櫎
@@ -83,6 +108,12 @@
     }
 
     @Override
+    public List<SrmSupplierDO> getSupplierAllList() {
+        return supplierMapper.selectList(new LambdaQueryWrapperX<SrmSupplierDO>()
+                .orderByAsc(SrmSupplierDO::getId));
+    }
+
+    @Override
     public List<SrmSupplierDO> getSupplierList(Collection<Long> ids) {
         if (CollUtil.isEmpty(ids)) {
             return Collections.emptyList();
@@ -105,4 +136,15 @@
         }
     }
 
+    private void validateNoReference(Long id) {
+        boolean hasReference = supplierApplyMapper.existsApprovedBySupplierId(id)
+                || supplierCertificateMapper.selectCount(SrmSupplierCertificateDO::getSupplierId, id) > 0
+                || tenderBidMapper.selectCount(SrmTenderBidDO::getSupplierId, id) > 0
+                || bidEvaluationMapper.selectCount(SrmBidEvaluationDO::getSupplierId, id) > 0
+                || bidAwardMapper.selectCount(SrmBidAwardDO::getSupplierId, id) > 0;
+        if (hasReference) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_EXISTS_REFERENCE);
+        }
+    }
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
index f005359..aa836d3 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmBidAwardServiceImpl.java
@@ -14,6 +14,7 @@
 import cn.iocoder.yudao.module.srm.enums.BidStatusEnum;
 import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
+import cn.iocoder.yudao.module.srm.service.apply.SrmSupplierApplyService;
 import com.mzt.logapi.starter.annotation.LogRecord;
 import jakarta.annotation.Resource;
 import lombok.extern.slf4j.Slf4j;
@@ -61,6 +62,9 @@
 
     @Resource
     private ErpPurchaseOrderApi erpPurchaseOrderApi;
+
+    @Resource
+    private SrmSupplierApplyService supplierApplyService;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -162,6 +166,9 @@
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_SYNCED);
         }
 
+        // 渚涘簲鍟嗘湭鍑嗗叆鍒欒嚜鍔ㄥ噯鍏ュ苟閫氳繃锛屼繚璇侀噰璐鍗曟祦绋嬪彲鐢�
+        supplierApplyService.ensureSupplierApproved(srmSupplier.getId());
+
         // 3. 鏌ユ嫑鏍囩墿鏂欏垪琛�
         List<SrmTenderMaterialDO> materials = tenderMaterialMapper
                 .selectListByTenderProjectId(award.getTenderProjectId());
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
index f8ae67f..73b86db 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderProjectServiceImpl.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderCreateReqDTO;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderMaterialSaveReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPageReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderProjectPartialUpdateReqVO;
@@ -18,6 +19,7 @@
 import cn.iocoder.yudao.module.srm.enums.BidStatusEnum;
 import cn.iocoder.yudao.module.srm.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.srm.enums.TenderStatusEnum;
+import cn.iocoder.yudao.module.srm.service.apply.SrmSupplierApplyService;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
 import com.mzt.logapi.context.LogRecordContext;
 import com.mzt.logapi.starter.annotation.LogRecord;
@@ -41,6 +43,11 @@
 @Slf4j
 public class SrmTenderProjectServiceImpl implements SrmTenderProjectService {
 
+    /**
+     * 鎷涙爣椤圭洰璧勮川瑕佹眰闄勪欢鐨勪笟鍔¤褰曠被鍨嬶紝瀵瑰簲 {@link cn.iocoder.yudao.module.system.enums.storage.StorageRecordTypeEnum#SRM_TENDER_PROJECT}
+     */
+    public static final String TENDER_PROJECT_RECORD_TYPE = "srm_tender_project";
+
     @Resource
     private SrmTenderProjectMapper tenderProjectMapper;
     @Resource
@@ -60,8 +67,13 @@
     private MdmItemApi mdmItemApi;
     @Resource
     private ErpPurchaseOrderApi erpPurchaseOrderApi;
+    @Resource
+    private SrmSupplierApplyService supplierApplyService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public Long createTenderProject(SrmTenderProjectSaveReqVO createReqVO) {
         SrmTenderProjectDO project = BeanUtils.toBean(createReqVO, SrmTenderProjectDO.class);
         project.setTenderStatus(TenderStatusEnum.DRAFT.getCode());
@@ -69,10 +81,15 @@
             project.setBiddingMode(tenderConfigService.getConfig().getDefaultBiddingMode());
         }
         tenderProjectMapper.insert(project);
+        // 缁戝畾璧勮川瑕佹眰闄勪欢
+        if (CollUtil.isNotEmpty(createReqVO.getBlobIds())) {
+            storageAttachmentApi.bindAttachments("file", TENDER_PROJECT_RECORD_TYPE, project.getId(), createReqVO.getBlobIds());
+        }
         return project.getId();
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = LOG_TENDER_TYPE, subType = LOG_TENDER_UPDATE_SUB_TYPE,
             bizNo = "{{#updateReqVO.id}}", success = LOG_TENDER_UPDATE_SUCCESS)
     public void updateTenderProject(SrmTenderProjectSaveReqVO updateReqVO) {
@@ -84,6 +101,8 @@
             SrmTenderProjectDO updateObj = BeanUtils.toBean(updateReqVO, SrmTenderProjectDO.class);
             updateObj.setTenderStatus(status); // 鐘舵�佷笉鍏佽鍓嶇浼犲叆瑕嗙洊
             tenderProjectMapper.updateById(updateObj);
+            // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+            storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
             LogRecordContext.putVariable("tenderName", project.getTenderName());
             LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
             LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());
@@ -99,6 +118,8 @@
             updateObj.setTenderDesc(updateReqVO.getTenderDesc());
             updateObj.setQualificationRequirements(updateReqVO.getQualificationRequirements());
             tenderProjectMapper.updateById(updateObj);
+            // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+            storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
             LogRecordContext.putVariable("tenderName", project.getTenderName());
             LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
             LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());
@@ -112,6 +133,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteTenderProject(Long id) {
         SrmTenderProjectDO project = validateTenderExists(id);
         if (!TenderStatusEnum.DRAFT.getCode().equals(project.getTenderStatus())) {
@@ -121,6 +143,8 @@
         if (tenderBidMapper.countByTenderProjectId(id) > 0) {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_HAS_BIDS_CANNOT_DELETE);
         }
+        // 鍒犻櫎璧勮川瑕佹眰闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord(TENDER_PROJECT_RECORD_TYPE, id);
         tenderMaterialMapper.deleteByTenderProjectId(id);
         tenderProjectMapper.deleteById(id);
     }
@@ -284,6 +308,9 @@
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.AWARD_SUPPLIER_NOT_SYNCED);
         }
 
+        // 渚涘簲鍟嗘湭鍑嗗叆鍒欒嚜鍔ㄥ噯鍏ュ苟閫氳繃锛屼繚璇侀噰璐鍗曟祦绋嬪彲鐢�
+        supplierApplyService.ensureSupplierApproved(supplier.getId());
+
         // 鏌ユ嫑鏍囩墿鏂�
         List<SrmTenderMaterialDO> materials = tenderMaterialMapper
                 .selectListByTenderProjectId(award.getTenderProjectId());
@@ -368,6 +395,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     @LogRecord(type = LOG_TENDER_TYPE, subType = LOG_TENDER_UPDATE_SUB_TYPE,
             bizNo = "{{#updateReqVO.id}}", success = LOG_TENDER_UPDATE_SUCCESS)
     public void updateTenderProjectPartial(SrmTenderProjectPartialUpdateReqVO updateReqVO) {
@@ -380,6 +408,8 @@
         updateObj.setTenderDesc(updateReqVO.getTenderDesc());
         updateObj.setQualificationRequirements(updateReqVO.getQualificationRequirements());
         tenderProjectMapper.updateById(updateObj);
+        // 澧為噺鏇存柊璧勮川瑕佹眰闄勪欢
+        storageAttachmentApi.updateAttachments("file", TENDER_PROJECT_RECORD_TYPE, updateReqVO.getId(), updateReqVO.getBlobIds());
         LogRecordContext.putVariable("tenderName", project.getTenderName());
         LogRecordContext.putVariable("oldTenderDesc", project.getTenderDesc());
         LogRecordContext.putVariable("newTenderDesc", updateReqVO.getTenderDesc());
diff --git a/yudao-module-srm/src/main/resources/sql/srm_init.sql b/yudao-module-srm/src/main/resources/sql/srm_init.sql
index 2969d5a..faef6eb 100644
--- a/yudao-module-srm/src/main/resources/sql/srm_init.sql
+++ b/yudao-module-srm/src/main/resources/sql/srm_init.sql
@@ -95,6 +95,8 @@
     business_license_no VARCHAR(100) DEFAULT NULL COMMENT '钀ヤ笟鎵х収缂栧彿',
     tax_no VARCHAR(100) DEFAULT NULL COMMENT '绋庡彿',
     address VARCHAR(500) DEFAULT NULL COMMENT '鍏徃鍦板潃',
+    bank_name VARCHAR(255) DEFAULT NULL COMMENT '寮�鎴烽摱琛�',
+    bank_account VARCHAR(100) DEFAULT NULL COMMENT '閾惰璐﹀彿',
     contact_name VARCHAR(50) DEFAULT NULL COMMENT '鑱旂郴浜�',
     contact_mobile VARCHAR(20) DEFAULT NULL COMMENT '鑱旂郴鐢佃瘽',
     contact_email VARCHAR(100) DEFAULT NULL COMMENT '閭',
@@ -111,6 +113,7 @@
     finance_reviewer_id BIGINT DEFAULT NULL COMMENT '璐㈠姟瀹℃牳浜篒D',
     finance_review_time DATETIME DEFAULT NULL COMMENT '璐㈠姟瀹℃牳鏃堕棿',
     finance_review_opinion VARCHAR(500) DEFAULT NULL COMMENT '璐㈠姟瀹℃牳鎰忚',
+    remark VARCHAR(500) DEFAULT NULL COMMENT '澶囨敞',
     creator VARCHAR(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
     create_time DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
     updater VARCHAR(64) DEFAULT '' COMMENT '鏇存柊鑰�',
@@ -362,4 +365,3 @@
     KEY idx_supplier_id (supplier_id),
     KEY idx_purchase_order_id (purchase_order_id)
 ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='瀹氭爣璁板綍琛�';
-
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
index 599e513..0806302 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -60,7 +60,11 @@
     AFTER_SALE_RETURN("aftersales_after_sale_return", "鍞悗閫�璐х敵璇�"),
 
     // ===== MES =====
-    MES_PD_DOCUMENT("mes_pd_document", "MES 璁捐璧勬枡");
+    MES_PD_DOCUMENT("mes_pd_document", "MES 璁捐璧勬枡"),
+
+    // ===== SRM =====
+    SRM_SUPPLIER_CERTIFICATE("srm_supplier_certificate", "SRM 渚涘簲鍟嗚祫璐ㄨ瘉涔�"),
+    SRM_TENDER_PROJECT("srm_tender_project", "SRM 鎷涙爣椤圭洰");
 
     private final String type;
     private final String description;
diff --git a/yudao-server/src/main/resources/application-yzfx.yaml b/yudao-server/src/main/resources/application-yzfx.yaml
new file mode 100644
index 0000000..080012b
--- /dev/null
+++ b/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 鑺嬭壙锛歟asy-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 # 鍙傝锛歨ttps://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 閰嶇疆銆俁edisson 榛樿鐨勯厤缃冻澶熶娇鐢紝涓�鑸笉闇�瑕佽繘琛岃皟浼�
+  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 鑺嬭壙锛氬厛绂佺敤锛孲pring 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锛圚ttpClient 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鐨勬帴鍙f祴璇曞彿
+#    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锛圚ttpClient 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 # 灏忕▼搴忕増鏈�: 姝e紡鐗堜负 "release"锛涗綋楠岀増涓� "trial"锛涘紑鍙戠増涓� "develop"
+  wxa-subscribe-message:
+    miniprogram-state: developer # 璺宠浆灏忕▼搴忕被鍨嬶細寮�鍙戠増涓� 鈥渄eveloper鈥濓紱浣撻獙鐗堜负 鈥渢rial鈥濅负锛涙寮忕増涓� 鈥渇ormal鈥�
+  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 鍒嗛挓

--
Gitblit v1.9.3