From c2053d7017625f270577bc0f6c523d04f52db6ea Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 24 七月 2026 14:20:29 +0800
Subject: [PATCH] refactor(crm): 重构文件上传功能,替换原有的fileUrls字段为统一的StorageAttachmentApi

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java                     |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentSaveReqVO.java                 |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java                           |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java              |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java                         |    8 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java                  |    7 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordSaveReqVO.java                |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java           |    4 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java                |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java           |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java                 |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java                     |   12 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationRespVO.java                   |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java                  |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java            |    6 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java              |    4 
 docs/file_upload_guide.md                                                                                                              |  324 ++++------
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java                |   27 
 .claude/rules/file-upload.md                                                                                                           |  120 ++++
 sql/mysql/hrm_module_init.sql                                                                                                          |    5 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java       |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java           |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java                                              |   29 +
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java                            |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemMapper.java                                      |   21 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderBomMapper.java                       |    6 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/CrmFollowUpRecordServiceImpl.java                          |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderBomDO.java                      |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java                        |   16 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/archive/vo/MesPdArchiveDetailRespVO.java                |    5 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java                             |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java                    |    7 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java                      |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java                          |    3 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java                                |   11 
 docs/mdm_item_sync_mes_optimization.md                                                                                                 |   97 +++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java                           |    5 
 docs/附件上传模块汇总.md                                                                                                                       |  131 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java                   |    3 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java                                        |   17 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/CrmSaleQuotationController.java                  |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java                     |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/document/MesPdDocumentDO.java                             |    4 
 docs/attachment_upload_reform.md                                                                                                       |  168 +++++
 yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java                                              |   30 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java         |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java                    |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java                             |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java                               |    5 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java                             |    5 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java          |    3 
 docs/attachment_upload_frontend_integration.md                                                                                         |  306 ++++++++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java                                     |    4 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/dto/StorageBlobRespDTO.java                               |   16 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java                            |    6 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/quotation/CrmSaleQuotationServiceImpl.java                          |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java                          |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java                      |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java                   |   11 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java                                 |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/process/MesProWorkOrderProcessRespVO.java |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java                   |    7 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApi.java                                 |   17 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java        |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java                       |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java           |    5 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java              |    6 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApiImpl.java                             |   62 ++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java                   |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java        |   36 +
 70 files changed, 1,393 insertions(+), 344 deletions(-)

diff --git a/.claude/rules/file-upload.md b/.claude/rules/file-upload.md
new file mode 100644
index 0000000..c90d46a
--- /dev/null
+++ b/.claude/rules/file-upload.md
@@ -0,0 +1,120 @@
+# 鏂囦欢涓婁紶瑙勫垯
+
+## 鏍稿績鍘熷垯锛圓I 蹇呴』涓ユ牸閬靛畧锛�
+
+**鎵�鏈夋枃浠朵笂浼犲繀椤昏蛋 `yudao-module-system` 妯″潡鐨� Storage API + Attachment 涓棿琛ㄦ満鍒躲��**
+
+**绂佹鍦ㄤ换浣曚笟鍔℃ā鍧楄嚜琛岃璁℃枃浠朵笂浼犳柟妗堬紝鍖呮嫭浣嗕笉闄愪簬锛氭坊鍔� `file_url` 鍒椼�佹柊澧� upload 鎺ュ彛銆佹敞鍏� FileApi銆�**
+
+## 鏂囦欢涓庝笟鍔$殑鍏宠仈鏂瑰紡
+
+鏂囦欢涓庝笟鍔¤褰曠殑鍏宠仈**鍙兘閫氳繃 `system_storage_attachment` 涓棿琛�**锛屼笉鍏佽鍦ㄤ笟鍔¤〃涓坊鍔� `file_url`銆乣file_urls`銆乣attachment_url` 绛夊瓧绗︿覆瀛楁瀛樺偍鏂囦欢鍦板潃銆�
+
+鍏宠仈娴佺▼锛�
+1. 鍓嶇璋冪敤 `POST /system/storage-blob/upload` 涓婁紶鏂囦欢 鈫� 鑾峰緱 `blobId`
+2. 鍓嶇璋冪敤 `POST /system/storage-attachment/bind` 缁戝畾鏂囦欢鍒颁笟鍔¤褰曪紙浼犲叆 `recordType` + `recordId` + `blobItems`锛�
+3. 鍓嶇璋冪敤 `GET /system/storage-attachment/list` 鏌ヨ闄勪欢
+4. 鍓嶇璋冪敤 `DELETE /system/storage-attachment/delete` 鍒犻櫎闄勪欢
+
+## 姝g‘鎺ュ彛锛坰ystem 妯″潡锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/system/storage-blob/upload` | 涓婁紶鏂囦欢锛坄files` 鍙傛暟锛宮ultipart锛� |
+| POST | `/system/storage-blob/public-upload` | 鍏叡鏂囦欢涓婁紶锛堟案涔呮湁鏁堬級 |
+| GET | `/system/storage-blob/preview/{fileName}` | 鏂囦欢棰勮 |
+| GET | `/system/storage-blob/download/{fileName}` | 鏂囦欢涓嬭浇 |
+| GET | `/system/storage-attachment/list` | 鏌ヨ闄勪欢鍒楄〃 |
+| POST | `/system/storage-attachment/bind` | 缁戝畾闄勪欢鍒颁笟鍔¤褰� |
+| DELETE | `/system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+## 绂佹琛屼负锛圓I 涓ョ鎵ц浠ヤ笅鎿嶄綔锛�
+
+### 鏁版嵁搴撳眰闈�
+- 鉂� 绂佹鍦ㄤ笟鍔¤〃娣诲姞 `file_url`銆乣file_urls`銆乣attachment_url` 绛夋枃浠跺湴鍧�鍒�
+- 鉂� 绂佹鍦ㄤ笟鍔¤〃娣诲姞浠讳綍鐢ㄤ簬瀛樺偍鏂囦欢淇℃伅鐨� JSON 鍒�
+
+### Java 鍚庣灞傞潰
+- 鉂� 绂佹鍦� DO 绫绘坊鍔� `fileUrl`銆乣fileUrls`銆乣attachmentUrl` 绛夊瓧娈�
+- 鉂� 绂佹鍦� SaveReqVO / RespVO 娣诲姞鏂囦欢鍦板潃瀛楁
+- 鉂� 绂佹鍦� MES/CRM/ERP/HRM 绛変笟鍔℃ā鍧楁柊澧� `@PostMapping("/upload")` 鎺ュ彛
+- 鉂� 绂佹鍦� Controller 涓娇鐢� `MultipartFile` 鍙傛暟锛圗xcel 瀵煎叆闄ゅ锛�
+- 鉂� 绂佹娉ㄥ叆鎴栬皟鐢� `cn.iocoder.yudao.module.infra.api.file.FileApi`
+
+### 鍓嶇灞傞潰
+- 鉂� 绂佹璋冪敤 `/infra/file/upload` 鎴� `/infra/file/presigned-url`
+- 鉂� 绂佹璋冪敤鏃х増 `src/api/infra/file/index.ts` 涓殑 `uploadFile()` 鍑芥暟
+
+## 甯歌 AI 骞昏妯″紡锛堝繀椤昏瘑鍒苟鎷掔粷锛�
+
+浠ヤ笅鏄� AI 瀹规槗骞昏鐢熸垚鐨勮璁℃ā寮忥紝閬囧埌绫讳技闇�姹傛椂鐩存帴鎷掔粷锛�
+
+1. **"鍔犱釜 fileUrl 瀛楁灏辫"** 鈫� 閿欒锛屽繀椤荤敤 attachment 涓棿琛�
+2. **"鍙傝�冨叾瀹冩ā鍧楃殑 fileUrl 瀛楁"** 鈫� 閿欒锛屽叾瀹冩ā鍧楃殑 fileUrl 涔熷簲琚垹闄�
+3. **"鍦� Controller 鍔犱釜 upload 鎺ュ彛"** 鈫� 閿欒锛岀粺涓�璧� system 妯″潡
+4. **"鐢� FileApi 涓婁紶鏂囦欢"** 鈫� 閿欒锛孎ileApi 鏄� infra 妯″潡鐨勬棫鎺ュ彛
+5. **"鍦ㄨ〃鍗曚繚瀛樻帴鍙e姞 MultipartFile 鍙傛暟"** 鈫� 閿欒锛屼笂浼犲拰淇濆瓨鍒嗙
+
+## 姝g‘绀轰緥
+
+### 鍓嶇璋冪敤
+
+```typescript
+import { uploadFile, bindAttachments, listAttachments } from '#/api/system/storage';
+
+// 1. 涓婁紶鏂囦欢
+const blobs = await uploadFile([file]);
+const blobId = blobs[0].id;
+
+// 2. 淇濆瓨涓氬姟璁板綍鍚庯紝缁戝畾闄勪欢
+await bindAttachments({
+  application: 'file',
+  recordType: 'erp_purchase_order',
+  recordId: savedRecord.id,
+  blobItems: [{ blobId }],
+});
+
+// 3. 鏌ヨ闄勪欢
+const attachments = await listAttachments({
+  recordType: 'erp_purchase_order',
+  recordId: savedRecord.id,
+});
+```
+
+### recordType 鍛藉悕瑙勮寖
+
+鏍煎紡锛歚{妯″潡鍓嶇紑}_{涓氬姟琛ㄥ悕}`
+
+| 妯″潡 | recordType |
+|------|------------|
+| ERP 閲囪喘璁㈠崟 | `erp_purchase_order` |
+| ERP 閲囪喘鐢宠 | `erp_purchase_request` |
+| ERP 閿�鍞鍗� | `erp_sale_order` |
+| ERP 鏀舵鍗� | `erp_finance_receipt` |
+| ERP 浠樻鍗� | `erp_finance_payment` |
+| HRM 璇峰亣 | `hrm_leave_application` |
+| HRM 绂昏亴 | `hrm_resignation_application` |
+| HRM 璋冨姩 | `hrm_transfer_application` |
+| MES 閲囪喘璁㈠崟 | `mes_purchase_order` |
+| MES 宸ュ崟 | `mes_pro_work_order` |
+| CRM 瀹㈡埛 | `crm_customer` |
+
+## AI 鑷煡娓呭崟
+
+鍦ㄥ疄鐜颁换浣曟枃浠朵笂浼犵浉鍏抽渶姹傚墠锛岄�愰」纭锛�
+
+- [ ] 鏄惁浣跨敤浜� `/system/storage-blob/upload` 涓婁紶锛�
+- [ ] 鏄惁浣跨敤浜� `/system/storage-attachment/bind` 鍏宠仈锛�
+- [ ] 鏄惁娌℃湁鍦ㄤ笟鍔¤〃涓坊鍔犳枃浠跺湴鍧�鍒楋紵
+- [ ] 鏄惁娌℃湁鍦� DO/VO 涓坊鍔犳枃浠跺湴鍧�瀛楁锛�
+- [ ] 鏄惁娌℃湁鍒涘缓鏂扮殑 upload Controller 鎺ュ彛锛�
+- [ ] 鏄惁娌℃湁浣跨敤 infra 妯″潡鐨� FileApi锛�
+
+浠ヤ笂 6 椤瑰叏閮ㄤ负"鏄�"鎵嶅彲缁х画銆�
+
+## 涓轰粈涔�
+
+- 瀛樺偍閰嶇疆锛堟湰鍦�/S3锛夊湪 system 妯″潡缁熶竴绠$悊
+- 闄勪欢缁戝畾鏈哄埗锛坰torage-attachment锛夋彁渚� `recordType + recordId` 鐨勬爣鍑嗗叧鑱旀柟寮忥紝鏀寔澶氭枃浠躲�佺敤閫斿垎绫�
+- infra 妯″潡鐨� `/infra/file/*` 鏄棫鐗堟帴鍙o紝瀛樺湪鏂囦欢涓庝笟鍔¤褰曟棤娉曞叧鑱旂殑闂
+- 涓氬姟琛ㄥ瓨鍌� file_url 瀛楃涓叉棤娉曟敮鎸佸鏂囦欢锛屼笖鏂囦欢涓庝笟鍔¤褰曚箣闂寸己涔忔爣鍑嗗寲鐨勫叧鑱旂鐞�
\ No newline at end of file
diff --git a/docs/attachment_upload_frontend_integration.md b/docs/attachment_upload_frontend_integration.md
new file mode 100644
index 0000000..d8fafd3
--- /dev/null
+++ b/docs/attachment_upload_frontend_integration.md
@@ -0,0 +1,306 @@
+# 鏂囦欢涓婁紶鏀归�� - 鍓嶇鑱旇皟鏂规
+
+## 鍚庣鍙樻洿鎽樿
+
+鎵�鏈夋ā鍧楃殑鏂囦欢涓婁紶宸蹭粠 `fileUrl` 瀛楃涓插瓧娈垫敼涓� `system_storage_attachment` 涓棿琛ㄦ満鍒讹細
+
+| 鍙樻洿 | 鏃ф柟寮� | 鏂版柟寮� |
+|------|--------|--------|
+| 涓婁紶 | 鐩存帴瀛� fileUrl | POST `/system/storage-blob/upload` 鈫� 鑾峰彇 blobId |
+| 淇濆瓨 | SaveReqVO.fileUrl | SaveReqVO.blobIds (List\<Long\>) |
+| 鏌ヨ | RespVO.fileUrl | RespVO.attachmentList (List\<StorageBlob\>) |
+| 鍒犻櫎 | 鏃� | 鍚庣 Service 鑷姩鍚屾鍒犻櫎闄勪欢 |
+
+## 娑夊強椤甸潰
+
+### HRM 妯″潡锛�5涓〉闈級
+
+| 椤甸潰 | 鐩綍 | recordType |
+|------|------|------------|
+| 璇峰亣鐢宠 | `src/views/hrm/leave/` | `hrm_leave_application` |
+| 绂昏亴鐢宠 | `src/views/hrm/resignation/` | `hrm_resignation_application` |
+| 璋冨矖鐢宠 | `src/views/hrm/transfer/` | `hrm_transfer_application` |
+| 鑰冨嫟寮傚父鐢宠 | `src/views/hrm/attendance/exception/` | `hrm_attendance_exception` |
+| 钖祫鍙戞斁 | `src/views/hrm/salary/payment/` | `hrm_salary_payment` |
+
+### CRM 妯″潡锛�2涓〉闈級
+
+| 椤甸潰 | 鐩綍 | recordType |
+|------|------|------------|
+| 閿�鍞姤浠峰崟 | `src/views/crm/saleQuotation/` | `crm_sale_quotation` |
+| 璺熻繘璁板綍 | `src/views/crm/followup/` | `crm_follow_up_record` |
+
+### MES 妯″潡锛�1涓〉闈級
+
+| 椤甸潰 | 鐩綍 | recordType |
+|------|------|------------|
+| 璁捐璧勬枡 | `src/views/mes/pd/project/modules/document-form.vue` | `mes_pd_document` |
+
+---
+
+## API 璇存槑
+
+### 閫氱敤鏂囦欢 API锛堝凡鏈夛紝鏃犻渶鏂板锛�
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/system/storage-blob/upload` | 涓婁紶鏂囦欢锛坢ultipart锛宍files` 鍙傛暟锛� |
+| GET | `/system/storage-attachment/list` | 鏌ヨ闄勪欢鍒楄〃锛坄recordType` + `recordId`锛� |
+| POST | `/system/storage-attachment/bind` | 缁戝畾闄勪欢鍒颁笟鍔¤褰� |
+| DELETE | `/system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+鍓嶇宸叉湁瀵瑰簲鐨� API 灏佽鍦� `src/api/system/storage/index.ts`锛�
+- `uploadFile(files)` 鈫� `StorageBlob[]`
+- `listAttachments({ recordType, recordId })` 鈫� `StorageBlob[]`
+- `bindAttachments({ application, recordType, recordId, blobItems })` 鈫� `boolean`
+- `deleteAttachments(ids)` 鈫� `boolean`
+
+### 鍚勬帴鍙� SaveReqVO 鏂板瀛楁
+
+鎵�鏈夋ā鍧楃殑鍒涘缓/鏇存柊鎺ュ彛鍧囨柊澧� `blobIds` 鍙傛暟锛�
+
+| 鎺ュ彛 | 鏂板鍙傛暟 |
+|------|----------|
+| 鎵�鏈夋ā鍧� create/update | `blobIds: number[]` |
+
+### 鍚勬帴鍙� RespVO 鏂板瀛楁
+
+鎵�鏈夋ā鍧楃殑璇︽儏鏌ヨ鎺ュ彛鍧囨柊澧� `attachmentList` 瀛楁锛�
+
+| 鎺ュ彛 | 鏂板瀛楁 | 绫诲瀷 |
+|------|----------|------|
+| GET /get锛堣鎯咃級 | `attachmentList` | `StorageBlob[]` |
+
+**StorageBlob 缁撴瀯锛�**
+```typescript
+interface StorageBlob {
+  id: number;           // blob ID
+  url?: string;         // 鏂囦欢璁块棶 URL
+  previewURL?: string;  // 棰勮鍦板潃
+  name?: string;        // 鍘熸枃浠跺悕
+  downloadURL?: string; // 涓嬭浇鍦板潃
+  application?: string; // 鏂囦欢鐢ㄩ��
+  storageAttachmentId?: number; // 闄勪欢鍏宠仈 ID
+}
+```
+
+---
+
+## 鍓嶇淇敼姝ラ锛堟瘡涓ā鍧楅�氱敤锛�
+
+### 姝ラ 1锛氫慨鏀� API 绫诲瀷瀹氫箟
+
+淇敼 `src/api/{module}/index.ts` 涓殑鎺ュ彛瀹氫箟锛�
+
+```typescript
+// 鉁� 寮曞叆 StorageBlob 绫诲瀷
+import type { SystemStorageApi } from '#/api/system/storage';
+
+export namespace XxxApi {
+  export interface Xxx {
+    // ... 鍏朵粬瀛楁涓嶅彉 ...
+
+    // 鉂� 鍒犻櫎锛歠ileUrl?: string;
+    // 鉁� 鏂板锛�
+    blobIds?: number[];                          // 淇濆瓨鏃朵紶鍏ョ殑闄勪欢 blobId 鍒楄〃
+    attachmentList?: SystemStorageApi.StorageBlob[];  // 鏌ヨ鏃惰繑鍥炵殑闄勪欢鍒楄〃
+  }
+}
+```
+
+娑夊強鐨勬枃浠讹細
+
+| 鏂囦欢 | 鍒犻櫎瀛楁 | 鏂板瀛楁 |
+|------|---------|---------|
+| `src/api/hrm/leave/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/hrm/resignation/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/hrm/transfer/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/hrm/attendance/exception/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/hrm/salary/payment/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/crm/saleQuotation/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+| `src/api/crm/followup/index.ts` | `fileUrls` | `blobIds`, `attachmentList` |
+| `src/api/mes/pd/document/index.ts` | `fileUrl` | `blobIds`, `attachmentList` |
+
+### 姝ラ 2锛氫慨鏀硅〃鍗曢〉闈紙form.vue锛�
+
+**鏂板/缂栬緫妯″紡**闇�瑕佹坊鍔犳枃浠朵笂浼犵粍浠讹紝娴佺▼锛�
+
+```
+鐢ㄦ埛閫夋嫨鏂囦欢 鈫� uploadFile(files) 涓婁紶 鈫� 鑾峰緱 blobId[]
+鈫� 淇濆瓨鏃跺皢 blobIds 鍙戠粰鍚庣 鈫� 鍚庣鑷姩 bindAttachments
+```
+
+鏍稿績浠g爜妯″紡锛�
+
+```vue
+<script lang="ts" setup>
+import { ref } from 'vue';
+import { uploadFile } from '#/api/system/storage';
+import type { UploadChangeParam } from 'ant-design-vue';
+
+const blobIds = ref<number[]>([]);
+const fileList = ref([]);
+
+// 鏂囦欢涓婁紶澶勭悊
+async function handleUpload(info: UploadChangeParam) {
+  if (info.file.status === 'done') {
+    // 鏂囦欢涓婁紶鎴愬姛锛屾敹闆� blobId
+    const blobs = info.file.response;
+    if (blobs?.length) {
+      blobIds.value.push(blobs[0].id);
+    }
+  }
+}
+
+// 淇濆瓨鏃�
+async function handleSave() {
+  const data = await formApi.getValues();
+  data.blobIds = blobIds.value;  // 灏� blobIds 浼犵粰鍚庣
+  await createXxx(data);
+}
+
+// 缂栬緫鏃跺洖鏄惧凡鏈夐檮浠�
+async function loadDetail(id: number) {
+  const detail = await getXxx(id);
+  if (detail.attachmentList) {
+    fileList.value = detail.attachmentList.map(item => ({
+      uid: String(item.id),
+      name: item.name || '',
+      status: 'done',
+      url: item.url,
+    }));
+    blobIds.value = detail.attachmentList.map(item => item.id);
+  }
+}
+</script>
+
+<template>
+  <a-upload
+    v-model:file-list="fileList"
+    :action="`${baseURL}/system/storage-blob/upload`"
+    multiple
+    @change="handleUpload"
+  >
+    <a-button>
+      <upload-outlined />
+      涓婁紶闄勪欢
+    </a-button>
+  </a-upload>
+</template>
+```
+
+**鎴栬�呬娇鐢ㄥ凡缁忓皝瑁呯殑 uploadFile 鍑芥暟閰嶅悎鎵嬪姩涓婁紶锛�**
+- 鎺ㄨ崘鏂瑰紡锛氬湪鎻愪氦鍓嶅厛璋冪敤 `uploadFile()` 涓婁紶鎵�鏈夋枃浠讹紝鎷垮埌 blobIds 鍚庝竴璧锋彁浜�
+- 澶囬�夋柟寮忥細浣跨敤 a-upload 缁勪欢鐨勮嚜鍔ㄤ笂浼狅紝鍦� `@change` 涓敹闆� blobId
+
+### 姝ラ 3锛氫慨鏀硅鎯呭睍绀�
+
+鍦ㄨ鎯�/鏌ョ湅妯″紡涓嬪睍绀洪檮浠跺垪琛紙鍙锛夛細
+
+```vue
+<template>
+  <!-- 闄勪欢灞曠ず -->
+  <div v-if="attachmentList?.length">
+    <div class="text-gray-500 mb-2">闄勪欢</div>
+    <div v-for="item in attachmentList" :key="item.id">
+      <a :href="item.url" target="_blank">{{ item.name }}</a>
+    </div>
+  </div>
+</template>
+```
+
+---
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| `blobIds` | 琛ㄥ崟锛堜笉灞曠ず锛� | 闅愯棌瀛楁锛屼繚瀛樻椂浼犵粰鍚庣 |
+| `attachmentList` | 璇︽儏椤点�佽〃鍗曠紪杈戝洖鏄� | 鏄剧ず鏂囦欢鍒楄〃锛屾敮鎸侀瑙�/涓嬭浇 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鍒涘缓鏃� | 鍏堣皟鐢� `uploadFile()` 涓婁紶鏂囦欢鎷� blobId锛屽啀鎶� blobIds 浼犵粰 create 鎺ュ彛 |
+| 鏇存柊鏃� | 鍙互鏂板/鍒犻櫎闄勪欢锛屽悗绔敹鍒� blobIds 涓嶄负 null 鏃跺厛鍒犲悗缁� |
+| 鏌ョ湅鏃� | 璇︽儏鎺ュ彛杩斿洖 `attachmentList`锛岀洿鎺ユ覆鏌� |
+| 鍒犻櫎鏃� | 鍚庣鑷姩澶勭悊闄勪欢鍒犻櫎锛屽墠绔棤闇�棰濆鎿嶄綔 |
+| 鍒楄〃/鍒嗛〉 | 涓嶈繑鍥� `attachmentList`锛堟�ц兘鑰冭檻锛夛紝浠呰鎯呮帴鍙h繑鍥� |
+
+## 鍚勬ā鍧楃壒娈婅鏄�
+
+### HRM 璇峰亣/绂昏亴/璋冨矖
+- 琛ㄥ崟椤碉細`src/views/hrm/xxx/modules/form.vue`
+- 杩欎笁涓ā鍧楃粨鏋勭浉鍚岋紝鍙傝�冭鍋囧凡瀹屾垚鐨勯泦鎴愭柟寮�
+- 鍒涘缓鏃� `blobIds` 闅忚〃鍗曟暟鎹竴璧峰彂閫�
+- 鏇存柊鏃� `blobIds` 涓嶄负 null 鏃跺悗绔噸鏂扮粦瀹�
+
+### HRM 鑰冨嫟寮傚父鐢宠
+- 琛ㄥ崟椤碉細`src/views/hrm/attendance/exception/modules/form.vue`
+- 鐩墠鍙湁 create 鍜� update銆乨elete锛屾敞鎰� update 鏃朵篃闇�瑕佸鐞嗛檮浠堕噸缁�
+
+### HRM 钖祫鍙戞斁
+- 鍒楄〃椤碉細`src/views/hrm/salary/payment/index.vue`
+- 璇︽儏椤碉細`src/views/hrm/salary/payment/modules/detail.vue`
+- 钖祫鍙戞斁鏄悗绔嚜鍔ㄧ敓鎴愮殑锛屾病鏈夊垱寤�/缂栬緫琛ㄥ崟
+- **鍙渶鍦ㄨ鎯呴〉灞曠ず attachmentList**
+- 鍒犻櫎鏃跺悗绔嚜鍔ㄥ悓姝ュ垹闄ら檮浠�
+
+### CRM 閿�鍞姤浠峰崟
+- 琛ㄥ崟椤碉細`src/views/crm/saleQuotation/modules/form.vue`
+- 鍒楄〃椤电殑 items 鏄墿鏂欐槑缁嗭紝闄勪欢灞曠ず鍦ㄨ鎯�/缂栬緫寮圭獥涓�
+- 琛ㄥ崟宸茬粡浣跨敤 `useVbenModal` + `useVbenForm`锛岄渶瑕佸湪涓婁紶鍖哄煙鍔犲叆 a-upload
+
+### CRM 璺熻繘璁板綍
+- 琛ㄥ崟椤碉細`src/views/crm/followup/modules/form.vue`
+- 涔嬪墠浣跨敤 `fileUrls: string[]` 瀛樺偍闄勪欢鍦板潃锛岀幇鍦ㄦ敼涓� blobIds
+- 鍘熸潵鍙兘鏈夊浘鐗�/闄勪欢涓婁紶鍖哄煙锛岄渶瑕佹敼涓� storage API
+
+### MES 璁捐璧勬枡
+- 琛ㄥ崟椤碉細`src/views/mes/pd/project/modules/document-form.vue`
+- 璁捐璧勬枡鏈韩灏辨槸鏂囦欢涓婁紶鍦烘櫙锛堣祫鏂欑被鍨� + 鏂囦欢涓婁紶锛�
+- 娉ㄦ剰锛氳璁¤祫鏂欑殑"鏂囦欢"鏄笟鍔″唴瀹癸紝鑰� attachment 鏄檮鍔犵殑鍙傝�冩枃浠�
+- 鍘熸湁鐨� `fileUrl` 鏄祫鏂欐枃浠惰嚜韬殑鍦板潃锛岄渶纭鏄惁涔熼渶瑕佹敼涓� attachment 鏂瑰紡
+
+---
+
+## 鏂囦欢涓婁紶娴佺▼鏃跺簭
+
+```
+鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�     鈹屸攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+鈹� 鍓嶇     鈹�     鈹� system   鈹�     鈹� 涓氬姟妯″潡  鈹�
+鈹�          鈹�     鈹� 妯″潡     鈹�     鈹�          鈹�
+鈹斺攢鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�     鈹斺攢鈹�鈹�鈹�鈹攢鈹�鈹�鈹�鈹�鈹�
+     鈹�               鈹�               鈹�
+     鈹� 1.閫夋嫨鏂囦欢     鈹�               鈹�
+     鈹傗攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�>鈹�               鈹�
+     鈹� 2.杩斿洖 blobId 鈹�               鈹�
+     鈹�<鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�               鈹�
+     鈹�               鈹�               鈹�
+     鈹� 3.save(blobIds)               鈹�
+     鈹傗攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�>鈹�
+     鈹�               鈹�               鈹�
+     鈹�               鈹� 4.bind(blobIds)鈹�鈹�> 鍐呴儴璋冪敤
+     鈹�               鈹�<鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+     鈹�               鈹�               鈹�
+     鈹� 5.杩斿洖鎴愬姛     鈹�               鈹�
+     鈹�<鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+     鈹�               鈹�               鈹�
+     鈹� 6.鏌ョ湅璇︽儏     鈹�               鈹�
+     鈹傗攢鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�>鈹�
+     鈹�               鈹�               鈹�
+     鈹� 7.杩斿洖 attachmentList         鈹�
+     鈹�<鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�鈹�
+```
+
+## 娉ㄦ剰浜嬮」
+
+1. **涓婁紶鏃舵満**锛氭枃浠朵笂浼犲簲鐙珛浜庝笟鍔′繚瀛橈紝鍏堜笂浼犳嬁 blobId锛屽啀淇濆瓨涓氬姟鏁版嵁
+2. **缂栬緫鍥炴樉**锛氱紪杈戞椂蹇呴』閫氳繃 GET /get 鎺ュ彛鎷垮埌 attachmentList 鍥炴樉鍒颁笂浼犵粍浠�
+3. **澶ф枃浠跺鐞�**锛氬鏋滄枃浠惰緝澶э紝闇�瑕佸湪淇濆瓨鍓嶇瓑寰呬笂浼犲畬鎴愶紙loading 鐘舵�侊級
+4. **鍒犻櫎闄勪欢**锛氬墠绔彲浠ラ�氳繃 `deleteAttachments([attachmentId])` 鍒犻櫎鍗曚釜闄勪欢锛屼絾涓嶅己鍒讹紝鍥犱负淇濆瓨鏃� blobIds 涓嶄负 null 浼氳Е鍙戝悗绔噸鏂扮粦瀹�
+5. **钖祫鍙戞斁鐗规畩**锛氳柂璧勫彂鏀炬棤鍒涘缓/缂栬緫琛ㄥ崟锛屽彧闇�鍦ㄥ叾璇︽儏椤靛睍绀� attachmentList
+6. **MES 璁捐璧勬枡鐗规畩**锛歚MesPdDocumentDO` 鍘熸湁鐨� `fileUrl` 瀛楁宸茶鍒犻櫎锛屽鏋滆繖涓瓧娈垫槸浣滀负涓氬姟鏂囦欢鍦板潃锛堣�岄潪闄勪欢锛夛紝鍙兘闇�瑕佸崟鐙瘎浼版槸鍚﹂渶瑕佷繚鐣欐枃浠跺瓨鍌�
+7. **璺熻繘璁板綍鐗规畩**锛氳窡杩涜褰曚箣鍓嶇敤 `fileUrls: string[]`锛堟暟缁勶級锛岀幇鍦ㄦ敼涓� `blobIds: number[]`锛屾帴鍙e眰闈㈠彉鍖栬緝澶�
+8. **鍒楄〃椤典笉灞曠ず闄勪欢**锛氫负鎬ц兘鑰冭檻锛屽垪琛�/鍒嗛〉鎺ュ彛涓嶈繑鍥� attachmentList锛屽彧鍦ㄨ鎯呮帴鍙h繑鍥�
diff --git a/docs/attachment_upload_reform.md b/docs/attachment_upload_reform.md
new file mode 100644
index 0000000..31a638c
--- /dev/null
+++ b/docs/attachment_upload_reform.md
@@ -0,0 +1,168 @@
+# 闄勪欢涓婁紶妯″潡鏀归�� - 鍓嶇鑱旇皟鏂规
+
+## 姒傝堪
+
+鏈鏀归�犵粺涓�鎵�鏈夋ā鍧楃殑鏂囦欢涓婁紶锛�
+
+1. **鍚庣**锛氬垹闄や笟鍔¤〃涓殑 `file_url` 鍒楋紝鍏ㄩ儴鏀圭敤 `system_storage_attachment` 涓棿琛ㄥ叧鑱�
+2. **鍓嶇**锛欶ileUpload 缁勪欢缁熶竴浣跨敤 `/system/storage-blob/upload`锛岄檮浠堕�氳繃 attachment API 鍏宠仈
+
+## 鍚庣鍙樻洿
+
+### 宸插垹闄� file_url 鍒楃殑琛�
+
+| 琛ㄥ悕 | 妯″潡 |
+|------|------|
+| `erp_purchase_order` | ERP 閲囪喘璁㈠崟 |
+| `erp_purchase_request` | ERP 閲囪喘鐢宠 |
+| `erp_sale_order` | ERP 閿�鍞鍗� |
+| `hrm_leave_application` | HRM 璇峰亣 |
+| `hrm_resignation_application` | HRM 绂昏亴 |
+| `hrm_transfer_application` | HRM 璋冨姩 |
+
+### 宸蹭粠 Java 绫讳腑鍒犻櫎 fileUrl 瀛楁
+
+- 6 涓� DO 绫伙紙ErpPurchaseOrderDO銆丒rpPurchaseRequestDO銆丒rpSaleOrderDO銆丠rmLeaveApplicationDO銆丠rmResignationApplicationDO銆丠rmTransferApplicationDO锛�
+- 6 涓� SaveReqVO 绫�
+- 6 涓� RespVO 绫�
+
+### 鏂囦欢鍏宠仈鏂瑰紡
+
+鏂囦欢鍙兘閫氳繃 `system_storage_attachment` 涓棿琛ㄥ叧鑱旓細
+
+| API | 璇存槑 |
+|-----|------|
+| `POST /system/storage-blob/upload` | 涓婁紶鏂囦欢锛坄files` 鍙傛暟锛宮ultipart锛� |
+| `POST /system/storage-attachment/bind` | 缁戝畾闄勪欢鍒颁笟鍔¤褰� |
+| `GET /system/storage-attachment/list` | 鏌ヨ闄勪欢锛堟寜 recordType + recordId锛� |
+| `DELETE /system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+## 娑夊強椤甸潰
+
+### 涓�銆佹牳蹇冧笂浼犵粍浠舵敼鍔�
+
+**鏂囦欢**锛歚src/components/upload/use-upload.ts`
+
+| 鏀瑰姩椤� | 鏃у�� | 鏂板�� |
+|--------|------|------|
+| 涓婁紶鎺ュ彛 | `POST /infra/file/upload` / S3 鐩翠紶 | `POST /system/storage-blob/upload` |
+| 璇锋眰鏂瑰紡 | 鑷畾涔� `createFile0()` / PUT S3 | `FormData` + `requestClient.post()` |
+| uploadUrl | `${apiURL}/infra/file/upload` | `${apiURL}/system/storage-blob/upload` |
+
+### 浜屻�佷笂浼犻檺鍒舵斁寮�
+
+浠ヤ笅妯″潡鐨� `FileUpload` / `ImageUpload` 缁勪欢宸茬Щ闄� `maxNumber`銆乣maxSize`銆乣accept` 闄愬埗锛�
+
+#### ERP 妯″潡锛�13 涓枃浠讹級
+
+| 鏂囦欢 | 瀛楁 |
+|------|------|
+| `src/views/erp/purchase/order/data.ts` | `fileUrl` |
+| `src/views/erp/purchase/request/data.ts` | `fileUrl` |
+| `src/views/erp/purchase/return/data.ts` | `fileUrl` |
+| `src/views/erp/purchase/in/data.ts` | `fileUrl` |
+| `src/views/erp/sale/order/data.ts` | `fileUrl` |
+| `src/views/erp/sale/return/data.ts` | `fileUrl` |
+| `src/views/erp/sale/out/data.ts` | `fileUrl` |
+| `src/views/erp/stock/in/data.ts` | `fileUrl` |
+| `src/views/erp/stock/out/data.ts` | `fileUrl` |
+| `src/views/erp/stock/move/data.ts` | `fileUrl` |
+| `src/views/erp/stock/check/data.ts` | `fileUrl` |
+| `src/views/erp/finance/receipt/data.ts` | `fileUrl` |
+| `src/views/erp/finance/payment/data.ts` | `fileUrl` |
+
+#### ImageUpload 闄愬埗鏀惧紑锛�5 澶勶級
+
+| 鏂囦欢 | 瀛楁 |
+|------|------|
+| `src/views/im/manager/face/pack/data.ts` | `icon` |
+| `src/views/im/manager/face/pack/data.ts` | `url` |
+| `src/views/im/manager/channel/material/data.ts` | `coverUrl` |
+| `src/views/im/manager/channel/list/data.ts` | `avatar` |
+| `src/views/mes/md/item/modules/product-media-list.vue` | `url` |
+
+#### MES 鏂囨。闄愬埗鏀惧紑
+
+| 鏂囦欢 | 瀛楁 |
+|------|------|
+| `src/views/mes/pd/project/modules/document-form.vue` | `fileUrl` |
+
+### 涓夈�丠RM 闄勪欢涓婁紶淇
+
+3 涓� HRM 妯″潡鐨�"闄勪欢"瀛楁浠� `Input` 鏀逛负 `FileUpload`锛�
+
+| 鏂囦欢 | 瀛楁 | 鏀瑰姩 |
+|------|------|------|
+| `src/views/hrm/leave/data.ts` | `fileUrl` | `component: 'Input'` 鈫� `component: 'FileUpload'` |
+| `src/views/hrm/resignation/data.ts` | `fileUrl` | `component: 'Input'` 鈫� `component: 'FileUpload'` |
+| `src/views/hrm/transfer/data.ts` | `fileUrl` | `component: 'Input'` 鈫� `component: 'FileUpload'` |
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/system/storage-blob/upload` | 涓婁紶鏂囦欢锛宍files`锛坢ultipart 鏁扮粍锛� |
+| POST | `/system/storage-attachment/bind` | 缁戝畾闄勪欢鍒颁笟鍔¤褰� |
+| GET | `/system/storage-attachment/list` | 鏌ヨ涓氬姟璁板綍鐨勯檮浠跺垪琛� |
+| DELETE | `/system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+**涓婁紶鍝嶅簲**锛�
+
+```json
+[
+  {
+    "id": 123,
+    "url": "https://xxx.com/u_abc123.pdf",
+    "previewURL": "https://xxx.com/preview/u_abc123.pdf",
+    "name": "鍚堝悓.pdf",
+    "downloadURL": "https://xxx.com/download/u_abc123.pdf"
+  }
+]
+```
+
+**缁戝畾璇锋眰**锛�
+
+```json
+{
+  "application": "file",
+  "recordType": "mes_purchase_order",
+  "recordId": 1,
+  "blobItems": [{ "blobId": 123 }]
+}
+```
+
+## 鏍囧噯涓婁紶娴佺▼
+
+1. 鐢ㄦ埛閫夋嫨鏂囦欢 鈫� `POST /system/storage-blob/upload` 杩斿洖 `[{ id, url, name }]`
+2. 涓氬姟璁板綍淇濆瓨鏃� 鈫� `POST /system/storage-attachment/bind` 鍏宠仈 blobId
+3. 璇︽儏/缂栬緫椤� 鈫� `GET /system/storage-attachment/list` 鍔犺浇宸叉湁闄勪欢
+4. 鐢ㄦ埛鍒犻櫎闄勪欢 鈫� `DELETE /system/storage-attachment/delete`
+
+## 鍓嶇 API 灏佽
+
+鏂� API 妯″潡浣嶄簬 `src/api/system/storage/index.ts`锛�
+
+```typescript
+import { uploadFile, listAttachments, bindAttachments, deleteAttachments } from '#/api/system/storage';
+```
+
+## recordType 鍛藉悕瑙勮寖
+
+鏍煎紡锛歚{妯″潡鍓嶇紑}_{涓氬姟琛ㄥ悕}`
+
+| 妯″潡 | recordType | 璇存槑 |
+|------|------------|------|
+| MES 閲囪喘璁㈠崟 | `mes_purchase_order` | 閲囪喘璁㈠崟闄勪欢 |
+| MES 宸ュ崟 | `mes_pro_work_order` | 鐢熶骇宸ュ崟闄勪欢 |
+| MES 璁惧 | `mes_dv_machinery` | 璁惧妗f闄勪欢 |
+| MES 鏂囨。 | `mes_pd_document` | 椤圭洰鏂囨。闄勪欢 |
+| CRM 瀹㈡埛 | `crm_customer` | 瀹㈡埛闄勪欢 |
+| ERP 閲囪喘璁㈠崟 | `erp_purchase_order` | ERP 閲囪喘璁㈠崟闄勪欢 |
+
+## 娉ㄦ剰浜嬮」
+
+- FileUpload 缁勪欢浣跨敤 `POST /system/storage-blob/upload` 涓婁紶鏂囦欢锛孎ormData 瀛楁鍚嶄负 `files`
+- 鏂囦欢涓婁紶鍚庝粎鏆傚瓨锛屽繀椤昏皟鐢� bind 鎺ュ彛鍏宠仈鍒颁笟鍔¤褰�
+- 鏂囦欢棰勮浣跨敤鍝嶅簲涓殑 `previewURL` 鎴� `url` 瀛楁
+- 绂佹浣跨敤鏃х増 `/infra/file/*` 鎺ュ彛
+- HRM 璇峰亣/绂昏亴/璋冨姩椤甸潰鐨勯檮浠跺瓧娈电幇鍦ㄥ彲浠ヤ娇鐢� FileUpload 缁勪欢涓婁紶闄勪欢
diff --git a/docs/file_upload_guide.md b/docs/file_upload_guide.md
index c8a659e..21b9505 100644
--- a/docs/file_upload_guide.md
+++ b/docs/file_upload_guide.md
@@ -2,243 +2,171 @@
 
 ## 姒傝堪
 
-鏈郴缁熸彁渚涗簡**缁熶竴鐨勬枃浠朵笂浼犳湇鍔�**锛屾敮鎸佸绉嶅瓨鍌ㄦ柟寮忥紙鏈湴銆丼3/OSS銆佹暟鎹簱銆丗TP/SFTP锛夈��
+鏈郴缁熶娇鐢� `yudao-module-system` 妯″潡鐨� **Storage API** 浣滀负缁熶竴鐨勬枃浠朵笂浼犳湇鍔★紝鏀寔澶氱瀛樺偍鏂瑰紡锛堟湰鍦般�丼3/OSS锛夈��
 
-> **閲嶈**锛氭墍鏈変笟鍔℃ā鍧楀繀椤讳娇鐢ㄩ�氱敤鏂囦欢涓婁紶鎺ュ彛锛�**绂佹涓烘瘡涓ā鍧楁柊澧炰笂浼犳帴鍙�**銆�
+> **閲嶈**锛氭墍鏈変笟鍔℃ā鍧楀繀椤讳娇鐢� system 妯″潡鐨� Storage API锛�**绂佹浣跨敤 infra 妯″潡鐨� `/infra/file/*` 鎺ュ彛锛岀姝负姣忎釜妯″潡鏂板涓婁紶鎺ュ彛**銆�
 
 ---
 
-## 涓�銆侀�氱敤涓婁紶鎺ュ彛
+## 涓�銆侀�氱敤鎺ュ彛
 
-### 1.1 鎺ュ彛淇℃伅
+### 1.1 鏂囦欢涓婁紶
 
 | 鏂规硶 | 璺緞 | 璇存槑 |
 |------|------|------|
-| POST | `/infra/file/upload` | 閫氱敤鏂囦欢涓婁紶 |
+| POST | `/system/storage-blob/upload` | 閫氱敤鏂囦欢涓婁紶 |
 
-### 1.2 璇锋眰鍙傛暟
+**璇锋眰鍙傛暟**锛歚files`锛圡ultipartFile 鏁扮粍锛�
+
+**鍝嶅簲**锛�
+
+```json
+[
+  {
+    "id": 123,
+    "resourceKey": "abc123",
+    "contentType": "application/pdf",
+    "originalFilename": "鍚堝悓.pdf",
+    "uidFilename": "u_abc123.pdf",
+    "byteSize": 102400,
+    "path": "/storage/2024/01/abc123.pdf",
+    "previewURL": "https://xxx.com/preview/u_abc123.pdf",
+    "url": "https://xxx.com/u_abc123.pdf",
+    "name": "鍚堝悓.pdf",
+    "downloadURL": "https://xxx.com/download/u_abc123.pdf"
+  }
+]
+```
+
+### 1.2 闄勪欢缁戝畾
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/system/storage-attachment/bind` | 灏嗗凡涓婁紶鏂囦欢缁戝畾鍒颁笟鍔¤褰� |
+
+**璇锋眰鍙傛暟**锛�
 
 | 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
 |------|------|------|------|
-| file | MultipartFile | 鏄� | 涓婁紶鐨勬枃浠� |
-| directory | String | 鍚� | 瀛樺偍鐩綍锛屽 `purchase-request`銆乣contract` |
+| application | String | 鍚� | 鏂囦欢鐢ㄩ�旓紝濡� `file` |
+| recordType | String | 鏄� | 鍏宠仈璁板綍绫诲瀷锛屽 `mes_purchase_order` |
+| recordId | Long | 鏄� | 鍏宠仈璁板綍 ID |
+| blobItems | List\<BlobItem\> | 鏄� | 寰呯粦瀹氭枃浠跺垪琛� |
 
-### 1.3 鍝嶅簲绀轰緥
+`BlobItem`锛�
 
-```json
-{
-  "code": 0,
-  "data": "https://xxx.com/xxx.pdf",
-  "msg": "鎿嶄綔鎴愬姛"
-}
-```
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| blobId | Long | 鏄� | 涓婁紶杩斿洖鐨勬枃浠� ID |
+| application | String | 鍚� | 瑕嗙洊澶栧眰鐨勬枃浠剁敤閫� |
 
-杩斿洖鍊� `data` 鍗充负鏂囦欢璁块棶 URL锛屽彲鐩存帴瀛樺偍鍒版暟鎹簱銆�
+### 1.3 鏌ヨ闄勪欢鍒楄〃
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/system/storage-attachment/list` | 鏌ヨ涓氬姟璁板綍鐨勯檮浠� |
+
+**璇锋眰鍙傛暟**锛�
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| recordType | String | 鏄� | 鍏宠仈璁板綍绫诲瀷 |
+| recordId | Long | 鏄� | 鍏宠仈璁板綍 ID |
+| application | String | 鍚� | 鎸夌敤閫旇繃婊� |
+
+### 1.4 鍒犻櫎闄勪欢
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| DELETE | `/system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+**璇锋眰浣�**锛氭枃浠� ID 鏁扮粍 `[1, 2, 3]`
 
 ---
 
 ## 浜屻�佸墠绔泦鎴�
 
-### 2.1 涓婁紶缁勪欢绀轰緥
+### 2.1 浣跨敤灏佽濂界殑 API
 
-```html
-<el-upload
-  :action="uploadUrl"
-  :headers="headers"
-  :data="{ directory: 'purchase-request' }"
-  :on-success="handleUploadSuccess"
-  :before-upload="beforeUpload"
->
-  <el-button type="primary">鐐瑰嚮涓婁紶</el-button>
-</el-upload>
+```typescript
+import { bindAttachments, listAttachments, uploadFile } from '#/api/system/storage';
+
+// 1. 涓婁紶鏂囦欢
+const handleUpload = async (file: File) => {
+  const blobs = await uploadFile([file]);
+  // 鑾峰彇 blob ID
+  const blobId = blobs[0]!.id;
+  return blobId;
+};
+
+// 2. 缁戝畾鍒颁笟鍔¤褰�
+const handleBind = async (blobIds: number[], recordId: number) => {
+  await bindAttachments({
+    application: 'file',
+    recordType: 'mes_pro_work_order',
+    recordId,
+    blobItems: blobIds.map(id => ({ blobId: id })),
+  });
+};
+
+// 3. 鏌ヨ闄勪欢鍒楄〃
+const handleLoad = async (recordId: number) => {
+  const attachments = await listAttachments({
+    recordType: 'mes_pro_work_order',
+    recordId,
+  });
+  return attachments;
+};
 ```
 
-### 2.2 閰嶇疆璇存槑
+### 2.2 鏍囧噯娴佺▼
 
-```js
-data() {
-  return {
-    // 閫氱敤涓婁紶鎺ュ彛
-    uploadUrl: process.env.VUE_APP_BASE_API + '/infra/file/upload',
-    // 璁よ瘉澶�
-    headers: { Authorization: 'Bearer ' + getToken() },
-  }
-},
-methods: {
-  handleUploadSuccess(response) {
-    if (response.code === 0) {
-      this.form.fileUrl = response.data  // 淇濆瓨鏂囦欢 URL
-    }
-  },
-  beforeUpload(file) {
-    // 涓氬姟灞傛牎楠屾枃浠剁被鍨嬪拰澶у皬
-    const isLt10M = file.size / 1024 / 1024 < 10
-    if (!isLt10M) {
-      this.$message.error('鏂囦欢澶у皬涓嶈兘瓒呰繃 10MB!')
-    }
-    return isLt10M
-  }
-}
-```
+1. 鐢ㄦ埛閫夋嫨鏂囦欢 鈫� 璋冪敤 `POST /system/storage-blob/upload` 涓婁紶
+2. 涓氬姟璁板綍淇濆瓨鏃� 鈫� 璋冪敤 `POST /system/storage-attachment/bind` 缁戝畾闄勪欢鍒拌褰�
+3. 璇︽儏/缂栬緫椤� 鈫� 璋冪敤 `GET /system/storage-attachment/list` 鍔犺浇宸叉湁闄勪欢
+4. 鐢ㄦ埛鍒犻櫎闄勪欢 鈫� 璋冪敤 `DELETE /system/storage-attachment/delete`
+
+### 2.3 displayUpload 缁勪欢
+
+椤圭洰涓凡鏈夋枃浠朵笂浼犳樉绀虹粍浠讹紝鍙洿鎺ヤ娇鐢ㄧ郴缁� Storage API銆�
 
 ---
 
-## 涓夈�佺洰褰曡鑼�
+## 涓夈�乺ecordType 鍛藉悕瑙勮寖
 
-鍚勪笟鍔℃ā鍧楅�氳繃 `directory` 鍙傛暟鍖哄垎瀛樺偍鐩綍锛�**绂佹鏂板涓氬姟涓婁紶鎺ュ彛**锛�
+鏍煎紡锛歚{妯″潡鍓嶇紑}_{涓氬姟琛ㄥ悕}`
 
-| 妯″潡 | directory | 璇存槑 |
-|------|-----------|------|
-| 鐢ㄦ埛澶村儚 | `avatar` | 鐢ㄦ埛澶村儚鍥剧墖 |
-| 鍚堝悓闄勪欢 | `contract` | 鍚堝悓鐩稿叧鏂囦欢 |
-| 閲囪喘鐢宠 | `purchase-request` | 閲囪喘鐢宠闄勪欢 |
-| 閲囪喘璁㈠崟 | `purchase-order` | 閲囪喘璁㈠崟闄勪欢 |
-| 浜у搧鍥剧墖 | `product` | 浜у搧鐩稿叧鍥剧墖 |
-| 閫氱敤闄勪欢 | `attachment` | 閫氱敤涓氬姟闄勪欢 |
-
-> **鍛藉悕瑙勮寖**锛氫娇鐢ㄥ皬鍐欏瓧姣嶅拰杩炲瓧绗︼紝濡� `purchase-request`锛屼笌妯″潡鍚嶄繚鎸佷竴鑷淬��
+| 妯″潡 | recordType | 璇存槑 |
+|------|------------|------|
+| MES 閲囪喘璁㈠崟 | `mes_purchase_order` | 閲囪喘璁㈠崟闄勪欢 |
+| MES 宸ュ崟 | `mes_pro_work_order` | 鐢熶骇宸ュ崟闄勪欢 |
+| MES 璁惧 | `mes_dv_machinery` | 璁惧妗f闄勪欢 |
+| MES 鏂囨。 | `mes_pd_document` | 椤圭洰鏂囨。闄勪欢 |
+| CRM 瀹㈡埛 | `crm_customer` | 瀹㈡埛闄勪欢 |
+| ERP 浜у搧 | `erp_product` | 浜у搧闄勪欢 |
 
 ---
 
-## 鍥涖�佸悗绔泦鎴愶紙鐗规畩鍦烘櫙锛�
+## 鍥涖�佺姝㈣涓�
 
-### 4.1 鍚庣闇�瑕佷富鍔ㄤ笂浼犳枃浠舵椂
-
-鏋佸皯鏁板満鏅笅锛屽悗绔渶瑕佷富鍔ㄤ笂浼犳枃浠讹紙濡傜敓鎴愭姤琛ㄥ悗淇濆瓨锛夛紝鍙敞鍏� `FileApi`锛�
-
-```java
-@Resource
-private FileApi fileApi;
-
-// 涓婁紶鏂囦欢
-String url = fileApi.createFile(bytes, "鎶ヨ〃.xlsx", "report", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
-```
-
-### 4.2 鑾峰彇绉佹湁鏂囦欢棰勭鍚嶅湴鍧�
-
-```java
-// 鐢熸垚涓存椂璁块棶鍦板潃锛�1灏忔椂鏈夋晥
-String presignedUrl = fileApi.presignGetUrl(fileUrl, 3600);
-```
+- 鉂� 绂佹璋冪敤 `/infra/file/upload` 鎴� `/infra/file/presigned-url`
+- 鉂� 绂佹鍦� MES/CRM/ERP 绛変笟鍔℃ā鍧� Controller 鏂板 `@PostMapping("/upload")`
+- 鉂� 绂佹娉ㄥ叆 `cn.iocoder.yudao.module.infra.api.file.FileApi`
+- 鉂� 绂佹浣跨敤鏃х増 `src/api/infra/file/index.ts` 涓殑 `uploadFile()` 鍑芥暟
 
 ---
 
-## 浜斻�佸瓨鍌ㄩ厤缃�
+## 浜斻�佸墠绔� API 妯″潡浣嶇疆
 
-瀛樺偍閰嶇疆閫氳繃绠$悊鍚庡彴鍔ㄦ�侀厤缃紝鏃犻渶淇敼浠g爜锛�
+`src/api/system/storage/index.ts`
 
-1. 璁块棶 **鍩虹璁炬柦 > 鏂囦欢绠$悊 > 鏂囦欢閰嶇疆**
-2. 鏂板閰嶇疆锛岄�夋嫨瀛樺偍绫诲瀷骞跺~鍐欏弬鏁�
-3. 鐐瑰嚮"璁剧疆涓轰富閰嶇疆"婵�娲讳娇鐢�
-
-### 鏀寔鐨勫瓨鍌ㄧ被鍨�
-
-| 绫诲瀷 | 璇存槑 | 閫傜敤鍦烘櫙 |
-|------|------|----------|
-| DB | 瀛樺偍鍒版暟鎹簱 | 灏忔枃浠躲�佷复鏃舵枃浠� |
-| LOCAL | 鏈湴纾佺洏瀛樺偍 | 鍐呯綉閮ㄧ讲銆佸紑鍙戠幆澧� |
-| FTP/SFTP | FTP 鏈嶅姟鍣� | 鍏煎鑰佺郴缁� |
-| S3 | 闃块噷浜� OSS / 鑵捐浜� COS / MinIO | 鐢熶骇鐜 |
+瀵煎嚭鍑芥暟锛歚uploadFile`銆乣listAttachments`銆乣bindAttachments`銆乣deleteAttachments`
 
 ---
 
-## 鍏�佹枃浠跺瓨鍌ㄦ柟寮�
+## 鍏�佹敞鎰忎簨椤�
 
-### 6.1 澶氭枃浠跺瓨鍌紙鎺ㄨ崘锛�
-
-浣跨敤 `List<String>` + `StringListTypeHandler`锛屽弬鑰� CRM 璺熻繘璁板綍妯″潡锛�
-
-**瀹炰綋绫伙細**
-
-```java
-@TableName(value = "crm_follow_up_record", autoResultMap = true)
-public class CrmFollowUpRecordDO extends BaseDO {
-
-    /**
-     * 鍥剧墖
-     */
-    @TableField(typeHandler = StringListTypeHandler.class)
-    private List<String> picUrls;
-
-    /**
-     * 闄勪欢
-     */
-    @TableField(typeHandler = StringListTypeHandler.class)
-    private List<String> fileUrls;
-}
-```
-
-**鏁版嵁搴撳瓧娈�**锛歚VARCHAR`锛屽瓨鍌� JSON 鏁扮粍鏍煎紡濡� `["url1","url2","url3"]`
-
-**VO 绫伙細**
-
-```java
-@Schema(description = "闄勪欢")
-private List<String> fileUrls;
-```
-
-**鍓嶇浣跨敤锛�**
-
-```js
-// 涓婁紶鎴愬姛鍚庤拷鍔�
-handleUploadSuccess(response) {
-  if (response.code === 0) {
-    if (!this.form.fileUrls) {
-      this.form.fileUrls = []
-    }
-    this.form.fileUrls.push(response.data)
-  }
-}
-
-// 鍒犻櫎鏂囦欢
-handleRemove(index) {
-  this.form.fileUrls.splice(index, 1)
-}
-```
-
-### 6.2 鍗曟枃浠跺瓨鍌�
-
-```java
-// 瀹炰綋绫�
-private String fileUrl;
-
-// VO 绫�
-@Schema(description = "闄勪欢鍦板潃")
-private String fileUrl;
-```
-
-### 6.3 `infra_file` 琛ㄧ殑浣滅敤
-
-绯荤粺 `infra_file` 琛ㄨ褰曟墍鏈変笂浼犳枃浠剁殑鍏冩暟鎹紙name銆乸ath銆乽rl銆乼ype銆乻ize锛夛紝涓嶄笌涓氬姟鍏宠仈锛屼粎鐢ㄤ簬锛�
-- 鏂囦欢绠$悊鍚庡彴鏌ョ湅涓婁紶璁板綍
-- 缁熻瀛樺偍浣跨敤鎯呭喌
-
----
-
-## 涓冦�佹敞鎰忎簨椤�
-
-1. **浣跨敤閫氱敤鎺ュ彛**锛氭墍鏈夋枃浠朵笂浼犲繀椤讳娇鐢� `/infra/file/upload`锛岀姝㈡柊澧炰笟鍔′笂浼犳帴鍙�
-2. **鍖哄垎鐩綍**锛氶�氳繃 `directory` 鍙傛暟鍖哄垎涓氬姟妯″潡锛屼究浜庣鐞嗗拰娓呯悊
-3. **鏂囦欢澶у皬**锛氶粯璁ら檺鍒� 10MB锛屽彲鍦� Nginx 鎴栧簲鐢ㄥ眰璋冩暣
-4. **绫诲瀷鏍¢獙**锛氬墠绔簲鏍¢獙鍏佽鐨勬枃浠剁被鍨嬶紝闃叉涓婁紶鎭舵剰鏂囦欢
-5. **URL 瀛樺偍**锛氫笂浼犳垚鍔熷悗锛屽皢杩斿洖鐨� URL 瀛樺偍鍒颁笟鍔¤〃鐨� `fileUrl` 瀛楁
-
----
-
-## 涓冦�佸父瑙侀棶棰�
-
-**Q: 涓轰粈涔堜笉鑳戒负姣忎釜妯″潡鏂板涓婁紶鎺ュ彛锛�**
-
-A: 缁熶竴鎺ュ彛渚夸簬锛�
-- 缁熶竴绠$悊瀛樺偍閰嶇疆
-- 缁熶竴鏉冮檺鎺у埗鍜屽璁�
-- 閬垮厤浠g爜閲嶅
-- 鍚庣画缁存姢鍜岃縼绉�
-
-**Q: 濡備綍鍖哄垎涓嶅悓妯″潡鐨勬枃浠讹紵**
-
-A: 浣跨敤 `directory` 鍙傛暟锛屾枃浠朵細瀛樺偍鍦ㄥ搴旂洰褰曚笅銆�
-
-**Q: 鍓嶇濡備綍闄愬埗鏂囦欢绫诲瀷锛�**
-
-A: 鍦� `beforeUpload` 鏂规硶涓牎楠� `file.type` 鎴� `file.name` 鍚庣紑銆�
+1. **浣跨敤閫氱敤鎺ュ彛**锛氭墍鏈変笂浼犺蛋 system 妯″潡锛岄�氳繃 `recordType` 鍖哄垎涓氬姟
+2. **涓婁紶鍚庣粦瀹�**锛氭枃浠朵笂浼犲彧鏄殏瀛橈紝蹇呴』璋冪敤 bind 鎺ュ彛鍏宠仈鍒颁笟鍔¤褰�
+3. **鏂囦欢棰勮**锛氫娇鐢ㄥ搷搴斾腑鐨� `previewURL` 鎴� `url` 瀛楁
+4. **鏂囦欢涓嬭浇**锛氫娇鐢� `GET /system/storage-blob/download/{fileName}?token=xxx`
diff --git a/docs/mdm_item_sync_mes_optimization.md b/docs/mdm_item_sync_mes_optimization.md
new file mode 100644
index 0000000..fd6f6ba
--- /dev/null
+++ b/docs/mdm_item_sync_mes_optimization.md
@@ -0,0 +1,97 @@
+# MDM 鐗╂枡鍚屾 MES 浼樺寲 - 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 鍩虹鏁版嵁 > MDM > 鐗╂枡绠$悊锛堝垪琛ㄩ〉锛�
+- 鍩虹鏁版嵁 > MDM > 鐗╂枡绠$悊锛堟柊澧�/缂栬緫寮圭獥锛�
+
+## 鍙樻洿姒傝堪
+
+1. **绉婚櫎 `syncMes` 澶嶉�夋**锛氬垱寤�/缂栬緫鐗╂枡鏃朵笉鍐嶉渶瑕佹墜鍔ㄥ嬀閫�"鍚屾鍒� MES"锛屽悗绔嚜鍔ㄥ悓姝�
+2. **鍒楄〃鏂板鍚屾鐘舵�佸垪**锛氭樉绀虹墿鏂欐槸鍚﹀凡鍚屾鍒� MES
+3. **鐘舵��/鍒犻櫎瀹炴椂鍚屾**锛歁DM 渚ф洿鏂扮姸鎬佹垨鍒犻櫎鐗╂枡鏃讹紝鑷姩鍚屾鍒� MES
+
+## API 鍙樻洿
+
+### 1. GET `/mdm/item/page` - 鍝嶅簲鏂板瀛楁
+
+**鍝嶅簲瀛楁鏂板锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| syncedMes | Boolean | 鏄惁宸插悓姝ュ埌 MES銆倀rue=宸插悓姝ワ紝false/null=鏈悓姝� |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "list": [
+      {
+        "id": 1,
+        "code": "ITEM001",
+        "name": "铻轰笣",
+        "syncedMes": true,
+        "...": "..."
+      },
+      {
+        "id": 2,
+        "code": "ITEM002",
+        "name": "铻烘瘝",
+        "syncedMes": false,
+        "...": "..."
+      }
+    ],
+    "total": 100
+  }
+}
+```
+
+### 2. GET `/mdm/item/get` - 鍝嶅簲鏂板瀛楁
+
+鍚屼笂锛宍syncedMes` 琛ㄧず璇ョ墿鏂欐槸鍚﹀凡鍚屾鍒� MES銆�
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| syncedMes | 鍒楄〃鏂板鍒� | true 鏃舵樉绀虹豢鑹�"宸插悓姝�"鏍囩锛宖alse 鏃舵樉绀虹伆鑹�"鏈悓姝�"鏍囩 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鏂板鐗╂枡 | 鍚庣鑷姩鍚屾鍒� MES锛屽垪琛ㄤ腑 syncedMes 涓� true |
+| 缂栬緫鐗╂枡 | 鍚庣鑷姩鍚屾鍒� MES |
+| 鏇存柊鐗╂枡鐘舵�� | 鍚庣鑷姩鍚屾鐘舵�佸埌 MES锛堝惎鐢�/绂佺敤锛� |
+| 鍒犻櫎鐗╂枡 | 鍚庣鑷姩鍚屾鍒犻櫎 MES 涓殑瀵瑰簲璁板綍锛堝瀛樺湪锛� |
+| 琛ㄥ崟涓殑 syncMes 瀛楁 | 鍓嶇鍙繚鐣欎絾涓嶅啀鐢熸晥锛屽缓璁殣钘�"鍚屾鍒� MES"寮�鍏� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. TypeScript 绫诲瀷瀹氫箟
+
+`src/api/mdm/item/index.ts` 涓� `MdmItemApi.Item` 鎺ュ彛鏂板 `syncedMes?: boolean` 瀛楁銆�
+
+### 2. 鍒楄〃鏂板鍚屾鐘舵�佸垪
+
+`src/views/basicData/mdm/data.ts` 涓� `useGridColumns()` 鏂板涓�鍒楋細
+
+- field: `syncedMes`
+- title: "鍚屾鐘舵��"
+- 浣跨敤鎻掓Ы娓叉煋 Tag 缁勪欢
+
+### 3. 鍒楄〃椤垫ā鏉�
+
+`src/views/basicData/mdm/index.vue` 涓柊澧� `syncedMes` 鎻掓Ы妯℃澘锛屽睍绀哄悓姝ョ姸鎬佹爣绛俱��
+
+### 4. 琛ㄥ崟绉婚櫎 syncMes 寮�鍏筹紙鍙�夛級
+
+`src/views/basicData/mdm/data.ts` 涓� `useFormSchema()` 鍙殣钘忔垨绉婚櫎 `syncMes` 琛ㄥ崟椤癸紝鍚庣宸插拷鐣ユ鍙傛暟銆�
+
+## 娉ㄦ剰浜嬮」
+
+- 鍘嗗彶鏁版嵁涓湭鍚屾鍒� MES 鐨勭墿鏂欙紝`syncedMes` 涓� false锛涘彲閫氳繃缂栬緫鐗╂枡瑙﹀彂鑷姩鍚屾
+- MES 鍚屾澶辫触浼氬洖婊� MDM 鎿嶄綔锛堜繚鎸佹暟鎹竴鑷存�э級锛屽墠绔渶鍏虫敞鎺ュ彛杩斿洖鐨勯敊璇俊鎭�
+- `syncedMes` 瀛楁鍦ㄨ鎯呴〉锛坓etItem锛変篃浼氳繑鍥烇紝鍙敤浜庤鎯呴〉鏄剧ず鍚屾鐘舵��
diff --git "a/docs/\351\231\204\344\273\266\344\270\212\344\274\240\346\250\241\345\235\227\346\261\207\346\200\273.md" "b/docs/\351\231\204\344\273\266\344\270\212\344\274\240\346\250\241\345\235\227\346\261\207\346\200\273.md"
new file mode 100644
index 0000000..1287927
--- /dev/null
+++ "b/docs/\351\231\204\344\273\266\344\270\212\344\274\240\346\250\241\345\235\227\346\261\207\346\200\273.md"
@@ -0,0 +1,131 @@
+# 闄勪欢涓婁紶妯″潡姹囨��
+
+## 涓�銆佹牳蹇冧笂浼犵粍浠�
+
+璺緞锛歚src/components/upload/`
+
+| 缁勪欢 | 鏂囦欢 | 鐢ㄩ�� |
+|---|---|---|
+| `FileUpload` | `file-upload.vue` | 閫氱敤鏂囦欢涓婁紶锛屾枃鏈垪琛ㄥ睍绀猴紝鏀寔鎷栨嫿銆佺被鍨�/澶у皬鏍¢獙 |
+| `ImageUpload` | `image-upload.vue` | 鍥剧墖涓婁紶锛屽崱鐗囬瑙堟ā寮忥紝鏀寔鍥剧墖棰勮寮圭獥 |
+| `InputUpload` | `input-upload.vue` | 杈撳叆妗�+涓婁紶缁勫悎锛屼笂浼犳寜閽湪杈撳叆妗嗗悗缂�鎴栨枃鏈煙鍙充笅瑙� |
+| `useUpload()` | `use-upload.ts` | 鏍稿績缁勫悎鍑芥暟锛岀粺涓�浣跨敤 system 妯″潡 Storage API锛圥OST `/system/storage-blob/upload`锛� |
+| `CropperAvatar` | `src/components/cropper/cropper-avatar.vue` | 澶村儚瑁佸壀涓婁紶 |
+
+### 涓婁紶妯″紡
+
+- **鏈嶅姟绔笂浼�**锛歅OST `/system/storage-blob/upload`锛岀粺涓�浣跨敤 system 妯″潡 Storage API
+- **闄勪欢缁戝畾**锛歅OST `/system/storage-attachment/bind`锛屽皢涓婁紶鐨勬枃浠跺叧鑱斿埌涓氬姟璁板綍
+
+### 琛ㄥ崟閫傞厤鍣ㄦ敞鍐�
+
+璺緞锛歚src/adapter/component/index.ts`
+
+娉ㄥ唽浜� `Upload`銆乣FileUpload`銆乣ImageUpload`銆乣PreviewUpload` 鍥涚琛ㄥ崟缁勪欢绫诲瀷锛屽彲鍦ㄨ〃鍗晄chema涓洿鎺ヤ娇鐢ㄣ��
+
+---
+
+## 浜屻�丗ileUpload 浣跨敤妯″潡锛堥檮浠朵笂浼狅級
+
+### ERP 妯″潡
+
+| 妯″潡 | 鏂囦欢 | 瀛楁 | 闄愬埗 |
+|---|---|---|---|
+| 閲囪喘璁㈠崟 | `src/views/erp/purchase/order/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閲囪喘鐢宠 | `src/views/erp/purchase/request/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閲囪喘閫�璐� | `src/views/erp/purchase/return/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閲囪喘鍏ュ簱 | `src/views/erp/purchase/in/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閿�鍞鍗� | `src/views/erp/sale/order/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閿�鍞��璐� | `src/views/erp/sale/return/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 閿�鍞嚭搴� | `src/views/erp/sale/out/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 搴撳瓨鍏ュ簱 | `src/views/erp/stock/in/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 搴撳瓨鍑哄簱 | `src/views/erp/stock/out/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 搴撳瓨璋冩嫧 | `src/views/erp/stock/move/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 搴撳瓨鐩樼偣 | `src/views/erp/stock/check/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 鏀舵鍗� | `src/views/erp/finance/receipt/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+| 浠樻鍗� | `src/views/erp/finance/payment/data.ts` | `fileUrl` | 鏃犻檺鍒� |
+
+### CRM 妯″潡
+
+| 妯″潡 | 鏂囦欢 | 瀛楁 | 闄愬埗 |
+|---|---|---|---|
+| 璺熻繘璁板綍 | `src/views/crm/followup/data.ts` | `fileUrls` | 鈥� |
+
+### MES 妯″潡
+
+| 妯″潡 | 鏂囦欢 | 瀛楁 | 闄愬埗 |
+|---|---|---|---|
+| 椤圭洰鏂囨。 | `src/views/mes/pd/project/modules/document-form.vue` | `fileUrl` | 鏃犻檺鍒�, directory:mes/pd |
+
+---
+
+## 涓夈�両mageUpload 浣跨敤妯″潡锛堝浘鐗囦笂浼狅級
+
+| 妯″潡 | 鏂囦欢 | 瀛楁 | 鐢ㄩ�� |
+|---|---|---|---|
+| AI瑙掕壊绠$悊 | `src/views/ai/model/chatRole/data.ts` | `avatar` | 瑙掕壊澶村儚 |
+| CRM璺熻繘璁板綍 | `src/views/crm/followup/data.ts` | `picUrls` | 鍥剧墖 |
+| 鍩虹婕旂ず | `src/views/infra/demo/demo01/data.ts` | `avatar` | 澶村儚 |
+| OAuth2搴旂敤 | `src/views/system/oauth2/client/data.ts` | `logo` | 搴旂敤鍥炬爣 |
+| IM琛ㄦ儏鍖� | `src/views/im/manager/face/pack/data.ts` | `icon` | 灏侀潰 |
+| IM琛ㄦ儏鍖� | `src/views/im/manager/face/pack/data.ts` | `url` | 琛ㄦ儏鍥� |
+| IM棰戦亾绱犳潗 | `src/views/im/manager/channel/material/data.ts` | `coverUrl` | 灏侀潰鍥� |
+| IM棰戦亾鍒楄〃 | `src/views/im/manager/channel/list/data.ts` | `avatar` | 棰戦亾澶村儚 |
+| MES浜у搧濯掍綋 | `src/views/mes/md/item/modules/product-media-list.vue` | `url` | 浜у搧鍥剧墖 |
+| BPM娴佺▼琛ㄥ崟 | `src/views/bpm/model/form/modules/basic-info.vue` | 鍥炬爣瀛楁 | 琛ㄥ崟鍥炬爣 |
+
+---
+
+## 鍥涖�乁pload 浣跨敤妯″潡锛圗xcel瀵煎叆锛�
+
+| 妯″潡 | 鏂囦欢 | 鐢ㄩ�� | API |
+|---|---|---|---|
+| 绯荤粺鐢ㄦ埛 | `src/views/system/user/modules/import-form.vue` | Excel瀵煎叆鐢ㄦ埛 | `POST /system/user/import` |
+| CRM瀹㈡埛 | `src/views/crm/customer/modules/import-form.vue` | Excel瀵煎叆瀹㈡埛 | `POST /crm/customer/import` |
+| MES鐗╂枡 | `src/views/mes/md/item/modules/import-form.vue` | Excel瀵煎叆鐗╂枡 | `POST /mes/md/item/import` |
+| MES璁惧 | `src/views/mes/dv/machinery/modules/import-form.vue` | Excel瀵煎叆璁惧 | `POST /mes/dv/machinery/import` |
+| HRM鑰冨嫟 | `src/views/hrm/attendance/record/modules/import-form.vue` | Excel瀵煎叆鑰冨嫟 | `POST /hrm/attendance/record/import` |
+
+---
+
+## 浜斻�佸叾浠栦笂浼犲満鏅�
+
+| 妯″潡 | 鏂囦欢 | 鐢ㄩ�� | 瀹炵幇鏂瑰紡 |
+|---|---|---|---|
+| AI鐭ヨ瘑搴� | `src/views/ai/knowledge/document/form/modules/upload-step.vue` | 鏂囨。涓婁紶锛圱XT/PDF/DOC绛夛紝15MB锛� | `UploadDragger` |
+| AI鑱婂ぉ | `src/views/ai/chat/index/modules/message/file-upload.vue` | 鏂囦欢涓婁紶锛�5涓�/10MB锛� | 鍘熺敓input + `useUpload().httpRequest()` |
+| IM鑱婂ぉ | `src/views/im/home/composables/useMediaUploader.ts` | 鍥剧墖/鏂囦欢/璇煶/瑙嗛涓婁紶 | `uploadFile()` API |
+| IM娑堟伅杈撳叆 | `src/views/im/home/pages/conversation/components/input/message-input.vue` | 閫夋嫨鏂囦欢鍙戦�� | 鍘熺敓input + `useMediaUploader` |
+| IM琛ㄦ儏涓婁紶 | `src/views/im/home/pages/conversation/components/input/face-picker.vue` | 鑷畾涔夎〃鎯呬笂浼� | `uploadFile()` API |
+| BPM绛惧悕 | `src/views/bpm/processInstance/detail/modules/signature.vue` | 鎵嬪啓绛惧悕涓婁紶 | `uploadFile()` API锛坆ase64杞枃浠讹級 |
+| 鐢ㄦ埛澶村儚 | `src/views/_core/profile/modules/profile-user.vue` | 澶村儚瑁佸壀涓婁紶 | `CropperAvatar` + `useUpload().httpRequest()` |
+| TinyMCE | `src/components/tinymce/img-upload.vue` | 瀵屾枃鏈浘鐗囦笂浼� | `Upload`缁勪欢 |
+| Tiptap | `src/packages/effects/plugins/src/tiptap/tiptap.vue` | 瀵屾枃鏈浘鐗囦笂浼� | Tiptap鎵╁睍 |
+| MES渚涘簲鍟� | `src/api/mes/md/vendor/index.ts` | Excel瀵煎叆渚涘簲鍟� | `POST /mes/md-vendor/import` |
+| MES瀹㈡埛 | `src/api/mes/md/client/index.ts` | Excel瀵煎叆MES瀹㈡埛 | `POST /mes/md-client/import` |
+| MES椤圭洰 | `src/api/mes/pd/project/index.ts` | 椤圭洰鏂囨。涓婁紶 | `PUT /mes/pd/project/upload` |
+
+---
+
+## 鍏�丠RM 闄勪欢涓婁紶锛堝凡淇锛�
+
+HRM 妯″潡鐨�"闄勪欢"瀛楁宸蹭粠 `Input` 鏀逛负 `FileUpload` 缁勪欢銆�
+
+| 妯″潡 | 鏂囦欢 | 瀛楁 | 缁勪欢 | 鐘舵�� |
+|---|---|---|---|---|
+| 璇峰亣 | `src/views/hrm/leave/data.ts` | `fileUrl` | `FileUpload` | 宸蹭慨澶� |
+| 绂昏亴 | `src/views/hrm/resignation/data.ts` | `fileUrl` | `FileUpload` | 宸蹭慨澶� |
+| 璋冨姩 | `src/views/hrm/transfer/data.ts` | `fileUrl` | `FileUpload` | 宸蹭慨澶� |
+
+---
+
+## 涓冦�佷笂浼犵浉鍏矨PI
+
+| 鍑芥暟 | 绔偣 | 鐢ㄩ�� |
+|---|---|---|
+| `uploadFile()` | `POST /system/storage-blob/upload` | 缁熶竴鏂囦欢涓婁紶 |
+| `bindAttachments()` | `POST /system/storage-attachment/bind` | 缁戝畾闄勪欢鍒颁笟鍔¤褰� |
+| `listAttachments()` | `GET /system/storage-attachment/list` | 鏌ヨ闄勪欢鍒楄〃 |
+| `deleteAttachments()` | `DELETE /system/storage-attachment/delete` | 鎵归噺鍒犻櫎闄勪欢 |
+
+API瀹氫箟鏂囦欢锛歚src/api/system/storage/index.ts`
diff --git a/sql/mysql/hrm_module_init.sql b/sql/mysql/hrm_module_init.sql
index 2510c7f..57c4d35 100644
--- a/sql/mysql/hrm_module_init.sql
+++ b/sql/mysql/hrm_module_init.sql
@@ -122,7 +122,6 @@
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
     `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
     `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
-    `file_url` 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 '鍒涘缓鏃堕棿',
@@ -150,7 +149,6 @@
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
     `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
     `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
-    `file_url` 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 '鍒涘缓鏃堕棿',
@@ -178,7 +176,6 @@
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
     `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
     `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
-    `file_url` 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 '鍒涘缓鏃堕棿',
@@ -255,7 +252,6 @@
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
     `process_instance_id` varchar(64) DEFAULT NULL COMMENT 'BPM娴佺▼瀹炰緥ID',
     `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
-    `file_url` 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 '鍒涘缓鏃堕棿',
@@ -359,7 +355,6 @@
     `payment_method` tinyint NOT NULL COMMENT '鍙戞斁鏂瑰紡锛�1-閾惰杞处锛�2-鐜伴噾',
     `payment_time` datetime NOT NULL COMMENT '鍙戞斁鏃堕棿',
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-寰呭彂鏀撅紝10-宸插彂鏀�',
-    `file_url` 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 '鍒涘缓鏃堕棿',
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
index 6739ee2..f09026a 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/CrmFollowUpRecordController.java
@@ -17,6 +17,7 @@
 import cn.iocoder.yudao.module.crm.service.contact.CrmContactService;
 import cn.iocoder.yudao.module.crm.service.followup.CrmFollowUpRecordService;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 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;
@@ -58,6 +59,8 @@
 
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓璺熻繘璁板綍")
@@ -85,7 +88,9 @@
                 getLoginUserId(), CrmPermissionLevelEnum.READ)) {
             throw exception(CRM_PERMISSION_DENIED, CrmBizTypeEnum.getNameByType(followUpRecord.getBizType()));
         }
-        return success(BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class));
+        CrmFollowUpRecordRespVO respVO = BeanUtils.toBean(followUpRecord, CrmFollowUpRecordRespVO.class);
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("crm_follow_up_record", id));
+        return success(respVO);
     }
 
     @GetMapping("/page")
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
index 075c3b3..9306ce5 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/followup/vo/CrmFollowUpRecordRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.crm.controller.admin.followup.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
 import cn.iocoder.yudao.module.crm.controller.admin.business.vo.business.CrmBusinessRespVO;
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
@@ -48,8 +49,8 @@
 
     @Schema(description = "鍥剧墖")
     private List<String> picUrls;
-    @Schema(description = "闄勪欢")
-    private List<String> fileUrls;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "鍒涘缓浜�", example = "1024")
     @ExcelProperty("鍒涘缓浜�")
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 ce14897..4550df6 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
@@ -35,6 +35,9 @@
     @NotNull(message = "涓嬫鑱旂郴鏃堕棿涓嶈兘涓虹┖")
     private LocalDateTime nextTime;
 
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
+
     @Schema(description = "鍏宠仈鐨勫晢鏈虹紪鍙锋暟缁�")
     private List<Long> businessIds;
     @Schema(description = "鍏宠仈鐨勮仈绯讳汉缂栧彿鏁扮粍")
@@ -42,7 +45,4 @@
 
     @Schema(description = "鍥剧墖 URL 鍒楄〃")
     private List<String> picUrls;
-    @Schema(description = "闄勪欢 URL 鍒楄〃")
-    private List<String> fileUrls;
-
 }
\ 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 2bad9a7..542f3e0 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
@@ -22,6 +22,7 @@
 import cn.iocoder.yudao.module.mdm.api.unit.dto.MdmUnitMeasureRespDTO;
 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.storage.StorageAttachmentApi;
 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;
@@ -65,6 +66,8 @@
     private MdmUnitMeasureApi mdmUnitMeasureApi;
     @Resource
     private CrmCustomerService customerService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓閿�鍞姤浠峰崟")
@@ -177,6 +180,8 @@
         }
         // 3. 濉厖鍏宠仈淇℃伅
         fillSaleQuotationRelatedFields(respVO);
+        // 4. 濉厖闄勪欢鍒楄〃
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("crm_sale_quotation", saleQuotation.getId()));
         return respVO;
     }
 
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 d9d037f..ad95d47 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
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.crm.controller.admin.quotation.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
 import io.swagger.v3.oas.annotations.media.Schema;
@@ -103,8 +104,8 @@
     @ExcelProperty("鍏宠仈鍚堝悓鍗曞彿")
     private String contractNo;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.baidu.com")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     @ExcelProperty("澶囨敞")
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
index a235d19..50323c1 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/quotation/vo/CrmSaleQuotationSaveReqVO.java
@@ -53,8 +53,8 @@
     @Schema(description = "绋庣巼", example = "13.00")
     private BigDecimal taxRate;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.baidu.com")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
index a01d47a..1202116 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/followup/CrmFollowUpRecordDO.java
@@ -73,12 +73,6 @@
     @TableField(typeHandler = StringListTypeHandler.class)
     private List<String> picUrls;
     /**
-     * 闄勪欢
-     */
-    @TableField(typeHandler = StringListTypeHandler.class)
-    private List<String> fileUrls;
-
-    /**
      * 鍏宠仈鐨勫晢鏈虹紪鍙锋暟缁�
      *
      * 鍏宠仈 {@link CrmBusinessDO#getId()}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
index 01329f2..0cb8e82 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/dal/dataobject/quotation/CrmSaleQuotationDO.java
@@ -125,10 +125,6 @@
      */
     private String contractNo;
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-    /**
      * 澶囨敞
      */
     private String remark;
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 31c62d3..69d88ca 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
@@ -18,6 +18,7 @@
 import cn.iocoder.yudao.module.crm.service.customer.CrmCustomerService;
 import cn.iocoder.yudao.module.crm.service.followup.bo.CrmFollowUpCreateReqBO;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -60,6 +61,8 @@
     @Resource
     @Lazy
     private CrmCustomerService customerService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @Override
     @CrmPermission(bizTypeValue = "#createReqVO.bizType", bizId = "#createReqVO.bizId", level = CrmPermissionLevelEnum.WRITE)
@@ -67,6 +70,8 @@
         // 1. 鍒涘缓鏇磋繘璁板綍
         CrmFollowUpRecordDO record = BeanUtils.toBean(createReqVO, CrmFollowUpRecordDO.class);
         crmFollowUpRecordMapper.insert(record);
+        storageAttachmentApi.bindAttachments("file", "crm_follow_up_record",
+                record.getId(), createReqVO.getBlobIds());
 
         // 2. 鏇存柊 bizId 瀵瑰簲鐨勮褰�
         if (ObjUtil.equal(CrmBizTypeEnum.CRM_CUSTOMER.getType(), record.getBizType())) { // 鏇存柊瀹㈡埛璺熻繘淇℃伅
@@ -113,12 +118,18 @@
             throw exception(FOLLOW_UP_RECORD_DELETE_DENIED);
         }
 
+        // 鍒犻櫎闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord("crm_follow_up_record", id);
         // 鍒犻櫎
         crmFollowUpRecordMapper.deleteById(id);
     }
 
     @Override
     public void deleteFollowUpRecordByBiz(Integer bizType, Long bizId) {
+        List<CrmFollowUpRecordDO> records = crmFollowUpRecordMapper.selectListByBiz(bizType, List.of(bizId));
+        for (CrmFollowUpRecordDO record : records) {
+            storageAttachmentApi.deleteAttachmentsByRecord("crm_follow_up_record", record.getId());
+        }
         crmFollowUpRecordMapper.deleteByBiz(bizType, bizId);
     }
 
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java
index dec219e..e6590bb 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/followup/bo/CrmFollowUpCreateReqBO.java
@@ -57,11 +57,6 @@
      */
     private List<String> picUrls;
     /**
-     * 闄勪欢
-     */
-    private List<String> fileUrls;
-
-    /**
      * 鍏宠仈鐨勫晢鏈虹紪鍙锋暟缁�
      *
      * 鍏宠仈 {@link CrmBusinessDO#getId()}
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/quotation/CrmSaleQuotationServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/quotation/CrmSaleQuotationServiceImpl.java
index 99dda59..c7bab33 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/quotation/CrmSaleQuotationServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/quotation/CrmSaleQuotationServiceImpl.java
@@ -23,6 +23,7 @@
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
 import cn.iocoder.yudao.module.crm.service.permission.CrmPermissionService;
 import cn.iocoder.yudao.module.crm.service.permission.bo.CrmPermissionCreateReqBO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
 import jakarta.annotation.Resource;
@@ -70,6 +71,8 @@
     @Resource
     private CrmPermissionService permissionService;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     private CrmContractService contractService;
 
     @Override
@@ -100,6 +103,8 @@
                 .setBizType(CrmBizTypeEnum.CRM_SALE_QUOTATION.getType())
                 .setBizId(saleQuotation.getId())
                 .setLevel(CrmPermissionLevelEnum.OWNER.getLevel()));
+        storageAttachmentApi.bindAttachments("file", "crm_sale_quotation",
+                saleQuotation.getId(), createReqVO.getBlobIds());
         return saleQuotation.getId();
     }
 
@@ -123,6 +128,11 @@
         saleQuotationMapper.updateById(updateObj);
         // 2.2 鏇存柊鎶ヤ环鏄庣粏
         updateQuotationProductList(updateReqVO.getId(), quotationProducts);
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("crm_sale_quotation", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "crm_sale_quotation",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
@@ -141,6 +151,8 @@
 
         // 2. 閬嶅巻鍒犻櫎
         saleQuotations.forEach(saleQuotation -> {
+            // 2.0 鍒犻櫎闄勪欢鍏宠仈
+            storageAttachmentApi.deleteAttachmentsByRecord("crm_sale_quotation", saleQuotation.getId());
             // 2.1 鍒犻櫎鎶ヤ环鍗�
             saleQuotationMapper.deleteById(saleQuotation.getId());
             // 2.2 鍒犻櫎鎶ヤ环鏄庣粏
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
index dae576f..7692ec1 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java
@@ -63,10 +63,6 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
     private BigDecimal depositPrice;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
-    @ExcelProperty("闄勪欢鍦板潃")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "浣犵寽")
     @ExcelProperty("澶囨敞")
     private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
index d259001..81a46ee 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java
@@ -32,9 +32,6 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", example = "7127")
     private BigDecimal depositPrice;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "浣犵寽")
     private String remark;
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
index 40fcab4..a0dd200 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java
@@ -64,9 +64,6 @@
     @Schema(description = "鍏ュ簱鐘舵�侊紙0:鏈叆搴� 1:閮ㄥ垎鍏ュ簱 2:鍏ㄩ儴鍏ュ簱锛�", example = "0")
     private Integer inStatus;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
index 97b9f8b..a405917 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestSaveReqVO.java
@@ -33,9 +33,6 @@
     @Schema(description = "瀹氶噾閲戦", example = "1000.00")
     private BigDecimal depositPrice;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
index f545a7b..4c7add4 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderRespVO.java
@@ -72,10 +72,6 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
     private BigDecimal depositPrice;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
-    @ExcelProperty("闄勪欢鍦板潃")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "浣犵寽")
     @ExcelProperty("澶囨敞")
     private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
index dcf7c68..841f14a 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderSaveReqVO.java
@@ -41,9 +41,6 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", example = "7127")
     private BigDecimal depositPrice;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://www.iocoder.cn")
-    private String fileUrl;
-
     @Schema(description = "澶囨敞", example = "浣犵寽")
     private String remark;
 
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
index 88a67a8..48266e3 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java
@@ -92,10 +92,6 @@
     private BigDecimal depositPrice;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
index ec47b9e..de4cde0 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseRequestDO.java
@@ -105,11 +105,6 @@
     private BigDecimal depositPrice;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
index be719e0..12e8daa 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/sale/ErpSaleOrderDO.java
@@ -106,10 +106,6 @@
     private BigDecimal depositPrice;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
index c69563e..23fd6dc 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationRespVO.java
@@ -3,8 +3,10 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 璇峰亣鐢宠 Response VO")
 @Data
@@ -58,8 +60,8 @@
     @Schema(description = "瀹℃壒瀹屾垚鏃堕棿", example = "2024-01-01 10:00:00")
     private LocalDateTime approveTime;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java
index ffc32a1..4409bb0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmLeaveApplicationSaveReqVO.java
@@ -7,6 +7,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 璇峰亣鐢宠鏂板/淇敼 Request VO")
 @Data
@@ -41,8 +42,8 @@
     @NotEmpty(message = "璇峰亣鍘熷洜涓嶈兘涓虹┖")
     private String reason;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
index 8ee359e..3c7aaa6 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationRespVO.java
@@ -3,8 +3,10 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 绂昏亴鐢宠 Response VO")
 @Data
@@ -70,8 +72,8 @@
     @Schema(description = "瀹℃壒瀹屾垚鏃堕棿", example = "2024-01-01 10:00:00")
     private LocalDateTime approveTime;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java
index acdafab..fb19057 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmResignationApplicationSaveReqVO.java
@@ -6,6 +6,7 @@
 import lombok.Data;
 
 import java.time.LocalDate;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 绂昏亴鐢宠鏂板/淇敼 Request VO")
 @Data
@@ -38,8 +39,8 @@
     @Schema(description = "浜ゆ帴璇存槑", example = "浜ゆ帴璇存槑")
     private String handoverRemark;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
index 1930b30..bb3e292 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationRespVO.java
@@ -3,8 +3,10 @@
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 璋冨矖鐢宠 Response VO")
 @Data
@@ -70,8 +72,8 @@
     @Schema(description = "瀹℃壒瀹屾垚鏃堕棿", example = "2024-01-01 10:00:00")
     private LocalDateTime approveTime;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
index 5c89ef3..293a5ce 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmTransferApplicationSaveReqVO.java
@@ -6,6 +6,7 @@
 import lombok.Data;
 
 import java.time.LocalDate;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 璋冨矖鐢宠鏂板/淇敼 Request VO")
 @Data
@@ -43,8 +44,8 @@
     @NotEmpty(message = "璋冨矖鍘熷洜涓嶈兘涓虹┖")
     private String reason;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
index 33c9f4a..3ca789b 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceExceptionController.java
@@ -13,6 +13,7 @@
 import cn.iocoder.yudao.module.hrm.service.employee.HrmEmployeeService;
 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.storage.StorageAttachmentApi;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -39,6 +40,8 @@
     private HrmEmployeeService employeeService;
     @Resource
     private DeptApi deptApi;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鏂板鑰冨嫟寮傚父鐢宠")
@@ -87,6 +90,7 @@
                 respVO.setDeptName(dept.getName());
             }
         }
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_attendance_exception", id));
         return success(respVO);
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
index 61f903d..6d41a13 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
@@ -9,6 +10,7 @@
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟寮傚父鐢宠 Response VO")
 @Data
@@ -66,8 +68,8 @@
     @ExcelProperty("瀹℃壒瀹屾垚鏃堕棿")
     private LocalDateTime approveTime;
 
-    @Schema(description = "闄勪欢鍦板潃")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞")
     @ExcelProperty("澶囨敞")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java
index 7bc5dda..64dd7d0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceExceptionSaveReqVO.java
@@ -7,6 +7,7 @@
 
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟寮傚父鐢宠鏂板/淇敼 Request VO")
 @Data
@@ -30,8 +31,8 @@
     @NotEmpty(message = "鐢宠鍘熷洜涓嶈兘涓虹┖")
     private String reason;
 
-    @Schema(description = "闄勪欢鍦板潃", example = "https://xxx.com/xxx.jpg")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
index 817f4f0..ebb1fdd 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryPaymentController.java
@@ -15,6 +15,7 @@
 import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryPaymentService;
 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.storage.StorageAttachmentApi;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -41,6 +42,8 @@
     private HrmEmployeeService employeeService;
     @Resource
     private DeptApi deptApi;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @GetMapping("/page")
     @Operation(summary = "鑾峰緱钖祫鍙戞斁鍒嗛〉")
@@ -56,7 +59,9 @@
     @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
     public CommonResult<HrmSalaryPaymentRespVO> getSalaryPayment(@RequestParam("id") Long id) {
         HrmSalaryPaymentDO salaryPayment = salaryPaymentService.getSalaryPayment(id);
-        return success(BeanUtils.toBean(salaryPayment, HrmSalaryPaymentRespVO.class));
+        HrmSalaryPaymentRespVO respVO = BeanUtils.toBean(salaryPayment, HrmSalaryPaymentRespVO.class);
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_salary_payment", id));
+        return success(respVO);
     }
 
     @PostMapping("/generate")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java
index 4fc9536..bb8bc14 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryPaymentRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
 import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
@@ -9,6 +10,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 钖祫鍙戞斁 Response VO")
 @Data
@@ -49,8 +51,8 @@
     @DictFormat("hrm_payment_status")
     private Integer status;
 
-    @Schema(description = "閾惰鍥炲崟闄勪欢")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞")
     @ExcelProperty("澶囨敞")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
index fb6d477..ceb56b0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmLeaveApplicationDO.java
@@ -95,11 +95,6 @@
     private LocalDateTime approveTime;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
index a2be939..9059bcf 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmResignationApplicationDO.java
@@ -108,11 +108,6 @@
     private java.time.LocalDateTime approveTime;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
index 7314a86..1378eae 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmTransferApplicationDO.java
@@ -105,11 +105,6 @@
     private java.time.LocalDateTime approveTime;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
index 8e9a4e9..db403d1 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/attendance/HrmAttendanceExceptionDO.java
@@ -83,11 +83,6 @@
     private LocalDateTime approveTime;
 
     /**
-     * 闄勪欢鍦板潃
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java
index ae7af88..2075954 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryPaymentDO.java
@@ -66,11 +66,6 @@
     private Integer status;
 
     /**
-     * 閾惰鍥炲崟闄勪欢
-     */
-    private String fileUrl;
-
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
index e2f527c..bda7f3a 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmLeaveApplicationServiceImpl.java
@@ -20,6 +20,7 @@
 import cn.iocoder.yudao.module.hrm.enums.HrmLeaveTypeEnum;
 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.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
@@ -65,6 +66,8 @@
     @Resource
     private BpmCategoryService bpmCategoryService;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Override
@@ -88,6 +91,9 @@
         application.setDeptId(createReqVO.getDeptId() != null ? createReqVO.getDeptId() : user.getDeptId());
         application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
         leaveApplicationMapper.insert(application);
+        // 缁戝畾闄勪欢
+        storageAttachmentApi.bindAttachments("file", "hrm_leave_application",
+                application.getId(), createReqVO.getBlobIds());
         return application.getId();
     }
 
@@ -115,6 +121,12 @@
         // 鏇存柊
         HrmLeaveApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmLeaveApplicationDO.class);
         leaveApplicationMapper.updateById(updateObj);
+        // 閲嶆柊缁戝畾闄勪欢
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("hrm_leave_application", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "hrm_leave_application",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
@@ -125,6 +137,8 @@
         if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
             throw exception(LEAVE_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
         }
+        // 鍒犻櫎闄勪欢鍏宠仈
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_leave_application", id);
         // 鍒犻櫎
         leaveApplicationMapper.deleteById(id);
     }
@@ -167,6 +181,8 @@
         respVO.setLeaveTypeName(HrmLeaveTypeEnum.getNameByStatus(application.getLeaveType()));
         // 濉厖瀹℃壒鐘舵�佸悕绉�
         respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+        // 濉厖闄勪欢鍒楄〃
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_leave_application", id));
         return respVO;
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
index 3548495..b0858ce 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmResignationApplicationServiceImpl.java
@@ -19,6 +19,7 @@
 import cn.iocoder.yudao.module.hrm.enums.HrmResignationTypeEnum;
 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.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import jakarta.annotation.Resource;
@@ -69,6 +70,8 @@
     @Resource
     private BpmCategoryService bpmCategoryService;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Override
@@ -95,6 +98,8 @@
         application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
         application.setHandoverStatus(HANDOVER_STATUS_NOT_STARTED);
         resignationApplicationMapper.insert(application);
+        storageAttachmentApi.bindAttachments("file", "hrm_resignation_application",
+                application.getId(), createReqVO.getBlobIds());
         return application.getId();
     }
 
@@ -118,6 +123,11 @@
         }
         HrmResignationApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmResignationApplicationDO.class);
         resignationApplicationMapper.updateById(updateObj);
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("hrm_resignation_application", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "hrm_resignation_application",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
@@ -126,6 +136,7 @@
         if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
             throw exception(RESIGNATION_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
         }
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_resignation_application", id);
         resignationApplicationMapper.deleteById(id);
     }
 
@@ -159,6 +170,7 @@
         respVO.setResignationTypeName(HrmResignationTypeEnum.getNameByStatus(application.getResignationType()));
         respVO.setHandoverStatusName(getHandoverStatusName(application.getHandoverStatus()));
         respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_resignation_application", id));
         return respVO;
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
index d63e58a..be91a86 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmTransferApplicationServiceImpl.java
@@ -19,6 +19,7 @@
 import cn.iocoder.yudao.module.hrm.enums.HrmTransferTypeEnum;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
 import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
@@ -69,6 +70,8 @@
     @Resource
     private BpmCategoryService bpmCategoryService;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Override
@@ -99,6 +102,8 @@
         application.setUserId(userId);
         application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
         transferApplicationMapper.insert(application);
+        storageAttachmentApi.bindAttachments("file", "hrm_transfer_application",
+                application.getId(), createReqVO.getBlobIds());
         return application.getId();
     }
 
@@ -129,6 +134,11 @@
         }
         HrmTransferApplicationDO updateObj = BeanUtils.toBean(updateReqVO, HrmTransferApplicationDO.class);
         transferApplicationMapper.updateById(updateObj);
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("hrm_transfer_application", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "hrm_transfer_application",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
@@ -137,6 +147,7 @@
         if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
             throw exception(TRANSFER_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
         }
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_transfer_application", id);
         transferApplicationMapper.deleteById(id);
     }
 
@@ -170,6 +181,7 @@
         fillPostNames(respVO, application);
         respVO.setTransferTypeName(HrmTransferTypeEnum.getNameByStatus(application.getTransferType()));
         respVO.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_transfer_application", id));
         return respVO;
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
index 89cfe32..0196b9b 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceExceptionServiceImpl.java
@@ -9,6 +9,7 @@
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceExceptionSaveReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceExceptionDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.attendance.HrmAttendanceExceptionMapper;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
 import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
 import jakarta.annotation.Resource;
@@ -39,6 +40,8 @@
     @Resource
     private BpmProcessInstanceApi bpmProcessInstanceApi;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     private BpmProcessDefinitionService bpmProcessDefinitionService;
 
     @Override
@@ -47,6 +50,8 @@
         attendanceException.setNo(generateNo());
         attendanceException.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
         attendanceExceptionMapper.insert(attendanceException);
+        storageAttachmentApi.bindAttachments("file", "hrm_attendance_exception",
+                attendanceException.getId(), createReqVO.getBlobIds());
         return attendanceException.getId();
     }
 
@@ -59,11 +64,17 @@
         validateAttendanceExceptionExists(updateReqVO.getId());
         HrmAttendanceExceptionDO updateObj = BeanUtils.toBean(updateReqVO, HrmAttendanceExceptionDO.class);
         attendanceExceptionMapper.updateById(updateObj);
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("hrm_attendance_exception", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "hrm_attendance_exception",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
     public void deleteAttendanceException(Long id) {
         validateAttendanceExceptionExists(id);
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_attendance_exception", id);
         attendanceExceptionMapper.deleteById(id);
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java
index 264a15c..b262fb6 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryPaymentServiceImpl.java
@@ -11,6 +11,7 @@
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper;
 import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -40,6 +41,8 @@
     private HrmSalaryCalculationMapper salaryCalculationMapper;
     @Resource
     private HrmNoRedisDAO noRedisDAO;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @Override
     public PageResult<HrmSalaryPaymentDO> getSalaryPaymentPage(HrmSalaryPaymentPageReqVO pageReqVO) {
@@ -280,6 +283,8 @@
         for (HrmSalaryPaymentDetailDO detail : details) {
             salaryPaymentDetailMapper.deleteById(detail.getId());
         }
+        // 鍒犻櫎闄勪欢鍏宠仈
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_salary_payment", id);
         // 鍒犻櫎鍙戞斁鍙拌处
         salaryPaymentMapper.deleteById(id);
     }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
index d271a28..282132b 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -18,6 +18,7 @@
 import cn.iocoder.yudao.module.mdm.service.item.MdmItemService;
 import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
 import cn.iocoder.yudao.module.mdm.service.warehouse.MdmWarehouseService;
+import cn.iocoder.yudao.module.mes.api.item.MesMdItemApi;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -41,6 +42,9 @@
 
     @Resource
     private MdmItemService itemService;
+
+    @Resource
+    private MesMdItemApi mesMdItemApi;
 
     @Resource
     private MdmItemCategoryService itemCategoryService;
@@ -105,6 +109,8 @@
             enrichItemRespVO(resp);
             // 濉厖鎵规灞炴�ч厤缃�
             enrichItemBatchConfig(resp);
+            // 濉厖鍚屾鐘舵��
+            resp.setSyncedMes(mesMdItemApi.getSyncedMdmItemIds(Set.of(id)).contains(id));
         }
         return success(resp);
     }
@@ -122,6 +128,9 @@
                 convertSet(pageResult.getList(), MdmItemDO::getUnitMeasureId));
         // 杞崲
         List<MdmItemRespVO> respList = BeanUtils.toBean(pageResult.getList(), MdmItemRespVO.class);
+        // 鎵归噺鏌ヨ鍚屾鐘舵��
+        Set<Long> allIds = convertSet(respList, MdmItemRespVO::getId);
+        Set<Long> syncedIds = mesMdItemApi.getSyncedMdmItemIds(allIds);
         // 鎵归噺鍏宠仈鏌ヨ杈呭崟浣嶅悕绉�
         Set<Long> unitIds = new HashSet<>();
         respList.forEach(resp -> {
@@ -161,6 +170,8 @@
             if (warehouse != null) {
                 resp.setWarehouseName(warehouse.getName());
             }
+            // 鍚屾鐘舵��
+            resp.setSyncedMes(syncedIds.contains(resp.getId()));
         });
         return success(new PageResult<>(respList, pageResult.getTotal()));
     }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
index c8674e3..b9709d3 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemRespVO.java
@@ -132,6 +132,9 @@
     @Schema(description = "澶囨敞", example = "澶囨敞淇℃伅")
     private String remark;
 
+    @Schema(description = "鏄惁宸插悓姝ュ埌 MES", example = "true")
+    private Boolean syncedMes;
+
     @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
     private LocalDateTime createTime;
 
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 503a0e4..14990a0 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
@@ -77,11 +77,9 @@
         // 淇濆瓨鎵规閰嶇疆锛堝鏋滄湁锛�
         saveBatchConfig(item.getId(), createReqVO);
 
-        // 鍚屾鍒� MES锛堝嬀閫� syncMes 鏃讹級
+        // 濮嬬粓鍚屾鍒� MES
         createReqVO.setId(item.getId());
-        if (Boolean.TRUE.equals(createReqVO.getSyncMes())) {
-            syncItemToMes(createReqVO);
-        }
+        syncItemToMes(createReqVO);
         return item.getId();
     }
 
@@ -100,10 +98,8 @@
         // 淇濆瓨鎵规閰嶇疆锛堝鏋滄湁锛�
         saveBatchConfig(updateReqVO.getId(), updateReqVO);
 
-        // 鍚屾鍒� MES锛堝嬀閫� syncMes 鏃讹級
-        if (Boolean.TRUE.equals(updateReqVO.getSyncMes())) {
-            syncItemToMes(updateReqVO);
-        }
+        // 濮嬬粓鍚屾鍒� MES
+        syncItemToMes(updateReqVO);
     }
 
     private void validateItemSaveData(MdmItemSaveReqVO reqVO) {
@@ -165,11 +161,14 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateItemStatus(Long id, Integer status) {
         // 鏍¢獙瀛樺湪
         validateItemExists(id);
         // 鏇存柊鐘舵��
         itemMapper.updateById(new MdmItemDO().setId(id).setStatus(status));
+        // 鍚屾鐘舵�佸埌 MES
+        mesMdItemApi.updateItemStatus(id, status);
     }
 
     @Override
@@ -181,6 +180,8 @@
         validateItemNoReference(id);
         // 杞垹闄ゅ綋鍓嶈褰�
         itemMapper.deleteById(id);
+        // 鍚屾鍒犻櫎鍒� MES
+        mesMdItemApi.deleteItem(id);
     }
 
     /**
diff --git a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
index a683eaf..aa030ce 100644
--- a/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
+++ b/yudao-module-mes-api/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApi.java
@@ -2,6 +2,9 @@
 
 import cn.iocoder.yudao.module.mes.api.item.dto.MesMdItemSyncReqDTO;
 
+import java.util.Collection;
+import java.util.Set;
+
 /**
  * MES 鐗╂枡浜у搧 API 鎺ュ彛
  *
@@ -29,4 +32,31 @@
      */
     void updateItem(MesMdItemSyncReqDTO syncReqDTO);
 
+    /**
+     * 鍒犻櫎 MES 鐗╂枡浜у搧锛堝悓姝ョ敤锛�
+     *
+     * 鎸� MDM 鐗╂枡 ID 鏌ユ壘 MES 璁板綍骞跺垹闄わ紝涓嶅瓨鍦ㄥ垯蹇界暐銆�
+     *
+     * @param mdmItemId MDM 鐗╂枡 ID
+     */
+    void deleteItem(Long mdmItemId);
+
+    /**
+     * 鏇存柊 MES 鐗╂枡浜у搧鐘舵�侊紙鍚屾鐢級
+     *
+     * 鎸� MDM 鐗╂枡 ID 鏌ユ壘 MES 璁板綍骞舵洿鏂扮姸鎬侊紝涓嶅瓨鍦ㄥ垯蹇界暐銆�
+     *
+     * @param mdmItemId MDM 鐗╂枡 ID
+     * @param status    鐘舵��
+     */
+    void updateItemStatus(Long mdmItemId, Integer status);
+
+    /**
+     * 鑾峰彇宸插悓姝ュ埌 MES 鐨� MDM 鐗╂枡 ID 闆嗗悎
+     *
+     * @param mdmItemIds MDM 鐗╂枡 ID 鍒楄〃
+     * @return 瀛樺湪浜� MES 涓殑 MDM 鐗╂枡 ID 闆嗗悎
+     */
+    Set<Long> getSyncedMdmItemIds(Collection<Long> mdmItemIds);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
index fe63450..c314980 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/api/item/MesMdItemApiImpl.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.md.item.MesMdItemMapper;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemBatchConfigService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
@@ -15,6 +16,9 @@
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
+
+import java.util.Collection;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.*;
@@ -34,6 +38,8 @@
 
     @Resource
     private MesMdItemService itemService;
+    @Resource
+    private MesMdItemMapper itemMapper;
     @Resource
     private MesMdItemTypeService itemTypeService;
     @Resource
@@ -106,6 +112,29 @@
         return saveReqVO;
     }
 
+    @Override
+    public void deleteItem(Long mdmItemId) {
+        log.info("[deleteItem] 鍚屾鍒犻櫎 MES 鐗╂枡浜у搧锛宮dmItemId={}", mdmItemId);
+        MesMdItemDO existItem = itemService.getItemByMdmItemId(mdmItemId);
+        if (existItem != null) {
+            itemService.deleteItem(existItem.getId());
+        }
+    }
+
+    @Override
+    public void updateItemStatus(Long mdmItemId, Integer status) {
+        log.info("[updateItemStatus] 鍚屾鏇存柊 MES 鐗╂枡浜у搧鐘舵�侊紝mdmItemId={}, status={}", mdmItemId, status);
+        MesMdItemDO existItem = itemService.getItemByMdmItemId(mdmItemId);
+        if (existItem != null) {
+            itemService.updateItemStatus(existItem.getId(), status);
+        }
+    }
+
+    @Override
+    public Set<Long> getSyncedMdmItemIds(Collection<Long> mdmItemIds) {
+        return itemMapper.selectMdmItemIdsByMdmItemIds(mdmItemIds);
+    }
+
     /**
      * 淇濆瓨鎵规閰嶇疆
      */
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/archive/vo/MesPdArchiveDetailRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/archive/vo/MesPdArchiveDetailRespVO.java
index 1ced7b7..46e8a87 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/archive/vo/MesPdArchiveDetailRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/archive/vo/MesPdArchiveDetailRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pd.archive.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 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;
@@ -122,8 +123,8 @@
         @Schema(description = "鏂囦欢鍚�", example = "design.pdf")
         private String fileName;
 
-        @Schema(description = "鏂囦欢URL", example = "/files/design.pdf")
-        private String fileUrl;
+        @Schema(description = "闄勪欢鍒楄〃")
+        private List<StorageBlobRespDTO> attachmentList;
 
         @Schema(description = "涓婁紶浜�", example = "1")
         private Long uploadUserId;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
index 3c84e72..d5ef60b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
@@ -13,6 +13,7 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pd.document.vo.MesPdDocumentSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
 import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 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;
@@ -45,6 +46,8 @@
 
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓璁捐璧勬枡")
@@ -79,7 +82,9 @@
         if (document == null) {
             return success(null);
         }
-        return success(buildDocumentRespVOList(Collections.singletonList(document)).get(0));
+        MesPdDocumentRespVO respVO = buildDocumentRespVOList(Collections.singletonList(document)).get(0);
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("mes_pd_document", id));
+        return success(respVO);
     }
 
     @GetMapping("/page")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
index 54668b6..f462b80 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pd.document.vo;
 
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 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;
@@ -9,6 +10,7 @@
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - MES 璁捐璧勬枡 Response VO")
 @Data
@@ -50,9 +52,8 @@
     @ExcelProperty("涓婁紶鏃堕棿")
     private LocalDateTime uploadTime;
 
-    @Schema(description = "鏂囦欢URL", example = "https://xxx/file.pdf")
-    @ExcelProperty("鏂囦欢URL")
-    private String fileUrl;
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
     @ExcelProperty("澶囨敞")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentSaveReqVO.java
index cbbc65e..56048e4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentSaveReqVO.java
@@ -6,6 +6,7 @@
 import jakarta.validation.constraints.NotBlank;
 import jakarta.validation.constraints.NotNull;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - MES 璁捐璧勬枡鏂板/淇敼 Request VO")
 @Data
@@ -37,8 +38,8 @@
     @Schema(description = "涓婁紶鏃堕棿")
     private LocalDateTime uploadTime;
 
-    @Schema(description = "鏂囦欢URL", example = "https://xxx/file.pdf")
-    private String fileUrl;
+    @Schema(description = "闄勪欢blobId鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
 
     @Schema(description = "澶囨敞", example = "娴嬭瘯澶囨敞")
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
index 7269c32..19dd295 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/MesProTaskController.java
@@ -254,15 +254,19 @@
                 convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
         Map<Long, CrmCustomerRespDTO> customerMap = customerApi.getCustomerMap(
                 convertSet(list, MesProTaskDO::getClientId));
-        // 宸ュ簭鐨� checkFlag锛氭壒閲忔煡璇㈠悗鏋勫缓 routeId -> processId -> checkFlag 鐨勫弻灞� Map
+        // 宸ュ簭鐨� checkFlag + backflushFlag锛氭壒閲忔煡璇㈠悗鏋勫缓 routeId -> processId -> flag 鐨勫弻灞� Map
         Set<Long> routeIds = convertSet(list, MesProTaskDO::getRouteId);
         Map<Long, Map<Long, Boolean>> routeProcessCheckFlagMap = new HashMap<>();
+        Map<Long, Map<Long, Boolean>> routeProcessBackflushFlagMap = new HashMap<>();
         if (CollUtil.isNotEmpty(routeIds)) {
             List<MesProRouteProcessDO> allRouteProcesses = routeProcessService.getRouteProcessListByRouteIds(routeIds);
             for (MesProRouteProcessDO rp : allRouteProcesses) {
                 routeProcessCheckFlagMap
                         .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>())
                         .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getCheckFlag()));
+                routeProcessBackflushFlagMap
+                        .computeIfAbsent(rp.getRouteId(), k -> new HashMap<>())
+                        .put(rp.getProcessId(), Boolean.TRUE.equals(rp.getBackflushFlag()));
             }
         }
         // 鎶ュ伐鍦ㄩ�旈噺锛氭寜浠诲姟鑱氬悎 feedbackQuantity锛堣崏绋�/瀹℃壒涓�/寰呮楠� = 鍦ㄩ�旓紱宸插畬鎴� = 宸插鎵癸級
@@ -303,6 +307,8 @@
                     vo.setClientName(customer.getName()));
             findAndThen(routeProcessCheckFlagMap, task.getRouteId(), processCheckMap ->
                     findAndThen(processCheckMap, task.getProcessId(), vo::setCheckFlag));
+            findAndThen(routeProcessBackflushFlagMap, task.getRouteId(), processBackflushMap ->
+                    findAndThen(processBackflushMap, task.getProcessId(), vo::setBackflushFlag));
             return vo;
         });
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
index b6f3266..eb3c490 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/task/vo/MesProTaskRespVO.java
@@ -121,6 +121,9 @@
     @Schema(description = "鏄惁璐ㄦ锛堟淳鐢熻嚜宸ヨ壓璺嚎宸ュ簭 checkFlag锛�", example = "true")
     private Boolean checkFlag;
 
+    @Schema(description = "鏄惁鍊掑啿锛堟淳鐢熻嚜宸ヨ壓璺嚎宸ュ簭 backflushFlag锛�", example = "true")
+    private Boolean backflushFlag;
+
     @Schema(description = "澶囨敞", example = "澶囨敞")
     private String remark;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java
index f9260ad..398cda6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderProcessController.java
@@ -6,14 +6,17 @@
 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.module.mes.controller.admin.pro.route.vo.process.MesProProcessBomItemVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.process.MesProWorkOrderProcessSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.pro.process.MesProProcessService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
@@ -37,6 +40,8 @@
 
     @Resource
     private MesProWorkOrderProcessService workOrderProcessService;
+    @Resource
+    private MesProWorkOrderBomService workOrderBomService;
     @Resource
     private MesProProcessService processService;
     @Resource
@@ -135,8 +140,32 @@
         Map<Long, MesMdItemDO> itemMap = CollUtil.isNotEmpty(outputItemIds)
                 ? itemService.getItemMap(outputItemIds) : Collections.emptyMap();
 
-        // 2. 鎷兼帴 VO
-        return BeanUtils.toBean(list, MesProWorkOrderProcessRespVO.class, vo -> {
+        // 2. 鏌ヨ鎶曟枡BOM锛堟寜宸ュ簭鍒嗙粍锛�
+        Set<Long> workOrderIds = convertSet(list, MesProWorkOrderProcessDO::getWorkOrderId);
+        workOrderIds.removeAll(Collections.singleton(null));
+        Map<Long, List<MesProProcessBomItemVO>> bomItemsByProcessId = new HashMap<>();
+        Set<Long> allBomItemIds = new HashSet<>();
+        for (Long woId : workOrderIds) {
+            List<MesProWorkOrderBomDO> bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(woId);
+            if (CollUtil.isNotEmpty(bomList)) {
+                allBomItemIds.addAll(convertSet(bomList, MesProWorkOrderBomDO::getItemId));
+                for (MesProWorkOrderBomDO bom : bomList) {
+                    bomItemsByProcessId.computeIfAbsent(bom.getProcessId(), k -> new ArrayList<>())
+                            .add(BeanUtils.toBean(bom, MesProProcessBomItemVO.class));
+                }
+            }
+        }
+        // 濉厖鎶曟枡鐗╂枡缂栫爜鍜屽悕绉�
+        Map<Long, MesMdItemDO> bomItemMap = CollUtil.isNotEmpty(allBomItemIds)
+                ? itemService.getItemMap(allBomItemIds) : Collections.emptyMap();
+        bomItemsByProcessId.values().forEach(vos -> vos.forEach(vo -> {
+            MapUtils.findAndThen(bomItemMap, vo.getItemId(), item -> {
+                vo.setItemCode(item.getCode()).setItemName(item.getName());
+            });
+        }));
+
+        // 3. 鎷兼帴 VO
+        List<MesProWorkOrderProcessRespVO> result = BeanUtils.toBean(list, MesProWorkOrderProcessRespVO.class, vo -> {
             MapUtils.findAndThen(processMap, vo.getProcessId(), process -> {
                 vo.setProcessCode(process.getCode()).setProcessName(process.getName());
             });
@@ -147,6 +176,9 @@
                 vo.setOutputItemCode(item.getCode()).setOutputItemName(item.getName());
             });
         });
+        // 璁剧疆姣忎釜宸ュ簭鍚勮嚜鐨勬姇鏂橞OM
+        result.forEach(vo -> vo.setBomItems(bomItemsByProcessId.getOrDefault(vo.getProcessId(), Collections.emptyList())));
+        return result;
     }
 
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/process/MesProWorkOrderProcessRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/process/MesProWorkOrderProcessRespVO.java
index bda4a62..e2806fd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/process/MesProWorkOrderProcessRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/process/MesProWorkOrderProcessRespVO.java
@@ -2,10 +2,12 @@
 
 import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
 import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.module.mes.controller.admin.pro.route.vo.process.MesProProcessBomItemVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - MES 鐢熶骇宸ュ崟宸ュ簭 Response VO")
 @Data
@@ -79,6 +81,9 @@
     @ExcelProperty("澶囨敞")
     private String remark;
 
+    @Schema(description = "宸ュ簭鎶曟枡BOM鏄庣粏")
+    private List<MesProProcessBomItemVO> bomItems;
+
     @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
     @ExcelProperty("鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/document/MesPdDocumentDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/document/MesPdDocumentDO.java
index 92f4e0f..8ee2ba7 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/document/MesPdDocumentDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pd/document/MesPdDocumentDO.java
@@ -65,10 +65,6 @@
      */
     private LocalDateTime uploadTime;
     /**
-     * 鏂囦欢URL
-     */
-    private String fileUrl;
-    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderBomDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderBomDO.java
index db63e65..09be14c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderBomDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/pro/workorder/MesProWorkOrderBomDO.java
@@ -36,6 +36,12 @@
      */
     private Long workOrderId;
     /**
+     * 宸ュ簭缂栧彿
+     *
+     * 鍏宠仈 {@link cn.iocoder.yudao.module.mes.dal.dataobject.pro.process.MesProProcessDO#getId()}
+     */
+    private Long processId;
+    /**
      * BOM 鐗╂枡缂栧彿
      *
      * 鍏宠仈 {@link MesMdItemDO#getId()}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemMapper.java
index e6c2fad..81d024d 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/md/item/MesMdItemMapper.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.mes.dal.mysql.md.item;
 
+import cn.hutool.core.collection.CollUtil;
 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;
@@ -8,6 +9,12 @@
 import org.apache.ibatis.annotations.Mapper;
 
 import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 
 /**
  * MES 鐗╂枡浜у搧 Mapper
@@ -47,4 +54,18 @@
         return selectOne(MesMdItemDO::getMdmItemId, mdmItemId);
     }
 
+    /**
+     * 鎵归噺鏌ヨ宸插瓨鍦ㄧ殑 MDM 鐗╂枡 ID 闆嗗悎
+     *
+     * @param mdmItemIds MDM 鐗╂枡 ID 鍒楄〃
+     * @return 瀛樺湪浜� mes_md_item 涓殑 mdmItemId 闆嗗悎
+     */
+    default Set<Long> selectMdmItemIdsByMdmItemIds(Collection<Long> mdmItemIds) {
+        if (CollUtil.isEmpty(mdmItemIds)) {
+            return Collections.emptySet();
+        }
+        List<MesMdItemDO> list = selectList(MesMdItemDO::getMdmItemId, mdmItemIds);
+        return convertSet(list, MesMdItemDO::getMdmItemId);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderBomMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderBomMapper.java
index 28f6f60..93c8eca 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderBomMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/workorder/MesProWorkOrderBomMapper.java
@@ -27,6 +27,12 @@
         return selectList(MesProWorkOrderBomDO::getWorkOrderId, workOrderId);
     }
 
+    default List<MesProWorkOrderBomDO> selectListByWorkOrderIdAndProcessId(Long workOrderId, Long processId) {
+        return selectList(new LambdaQueryWrapperX<MesProWorkOrderBomDO>()
+                .eq(MesProWorkOrderBomDO::getWorkOrderId, workOrderId)
+                .eqIfPresent(MesProWorkOrderBomDO::getProcessId, processId));
+    }
+
     default void deleteByWorkOrderId(Long workOrderId) {
         delete(MesProWorkOrderBomDO::getWorkOrderId, workOrderId);
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
index c733e24..d1d8172 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
@@ -7,6 +7,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pd.document.MesPdDocumentMapper;
 import cn.iocoder.yudao.module.mes.service.pd.project.MesPdProjectService;
+import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
@@ -32,6 +33,8 @@
     @Resource
     @Lazy
     private MesPdProjectService projectService;
+    @Resource
+    private StorageAttachmentApi storageAttachmentApi;
 
     @Override
     public Long createDocument(MesPdDocumentSaveReqVO createReqVO) {
@@ -40,6 +43,8 @@
         // 鎻掑叆
         MesPdDocumentDO document = BeanUtils.toBean(createReqVO, MesPdDocumentDO.class);
         documentMapper.insert(document);
+        storageAttachmentApi.bindAttachments("file", "mes_pd_document",
+                document.getId(), createReqVO.getBlobIds());
         return document.getId();
     }
 
@@ -50,12 +55,19 @@
         // 鏇存柊
         MesPdDocumentDO updateObj = BeanUtils.toBean(updateReqVO, MesPdDocumentDO.class);
         documentMapper.updateById(updateObj);
+        if (updateReqVO.getBlobIds() != null) {
+            storageAttachmentApi.deleteAttachmentsByRecord("mes_pd_document", updateReqVO.getId());
+            storageAttachmentApi.bindAttachments("file", "mes_pd_document",
+                    updateReqVO.getId(), updateReqVO.getBlobIds());
+        }
     }
 
     @Override
     public void deleteDocument(Long id) {
         // 鏍¢獙瀛樺湪
         validateDocumentExists(id);
+        // 鍒犻櫎闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord("mes_pd_document", id);
         // 鍒犻櫎
         documentMapper.deleteById(id);
     }
@@ -77,6 +89,10 @@
 
     @Override
     public void deleteByProjectId(Long projectId) {
+        List<MesPdDocumentDO> documents = documentMapper.selectListByProjectId(projectId);
+        for (MesPdDocumentDO document : documents) {
+            storageAttachmentApi.deleteAttachmentsByRecord("mes_pd_document", document.getId());
+        }
         documentMapper.deleteByProjectId(projectId);
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java
index e626b2e..9ee4e74 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/workorder/MesProWorkOrderProcessServiceImpl.java
@@ -191,32 +191,27 @@
     }
 
     /**
-     * 浠庡伐搴忓垪琛ㄤ腑鎻愬彇 BOM 鎶曟枡锛屽悎骞跺幓閲嶅悗淇濆瓨鍒板伐鍗� BOM 琛�
+     * 浠庡伐搴忓垪琛ㄤ腑鎻愬彇 BOM 鎶曟枡锛屾寜宸ュ簭鍒嗗埆淇濆瓨鍒板伐鍗� BOM 琛�
      */
     private void saveWorkOrderBomFromProcesses(Long workOrderId, List<MesProWorkOrderProcessSaveReqVO> list) {
-        // 鏀堕泦鎵�鏈夊伐搴忕殑 bomItems锛屾寜 itemId 鍘婚噸锛堜繚鐣欑涓�涓嚭鐜扮殑 quantity锛�
-        java.util.Map<Long, MesProWorkOrderProcessSaveReqVO.BomItem> merged = new java.util.LinkedHashMap<>();
+        workOrderBomService.deleteWorkOrderBomByWorkOrderId(workOrderId);
+        List<MesProWorkOrderBomDO> bomList = new ArrayList<>();
         for (MesProWorkOrderProcessSaveReqVO item : list) {
             if (CollUtil.isNotEmpty(item.getBomItems())) {
                 for (MesProWorkOrderProcessSaveReqVO.BomItem bomItem : item.getBomItems()) {
                     if (bomItem.getItemId() != null) {
-                        merged.putIfAbsent(bomItem.getItemId(), bomItem);
+                        bomList.add(MesProWorkOrderBomDO.builder()
+                                .workOrderId(workOrderId)
+                                .processId(item.getProcessId())
+                                .itemId(bomItem.getItemId())
+                                .quantity(bomItem.getQuantity())
+                                .remark(bomItem.getRemark())
+                                .build());
                     }
                 }
             }
         }
-        // 鍒犻櫎鏃� BOM锛屾壒閲忔彃鍏ユ柊 BOM
-        workOrderBomService.deleteWorkOrderBomByWorkOrderId(workOrderId);
-        if (CollUtil.isNotEmpty(merged)) {
-            List<MesProWorkOrderBomDO> bomList = new ArrayList<>(merged.size());
-            for (MesProWorkOrderProcessSaveReqVO.BomItem bomItem : merged.values()) {
-                bomList.add(MesProWorkOrderBomDO.builder()
-                        .workOrderId(workOrderId)
-                        .itemId(bomItem.getItemId())
-                        .quantity(bomItem.getQuantity())
-                        .remark(bomItem.getRemark())
-                        .build());
-            }
+        if (CollUtil.isNotEmpty(bomList)) {
             workOrderBomMapper.insertBatch(bomList);
         }
     }
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApi.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApi.java
new file mode 100644
index 0000000..3d26f6e
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApi.java
@@ -0,0 +1,17 @@
+package cn.iocoder.yudao.module.system.api.storage;
+
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
+
+import java.util.List;
+
+public interface StorageAttachmentApi {
+
+    void bindAttachments(String application, String recordType, Long recordId, List<Long> blobIds);
+
+    List<StorageBlobRespDTO> listAttachments(String recordType, Long recordId);
+
+    void deleteAttachments(List<Long> attachmentIds);
+
+    void deleteAttachmentsByRecord(String recordType, Long recordId);
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApiImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApiImpl.java
new file mode 100644
index 0000000..914c89d
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/StorageAttachmentApiImpl.java
@@ -0,0 +1,62 @@
+package cn.iocoder.yudao.module.system.api.storage;
+
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
+import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentListReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageAttachmentSaveReqVO;
+import cn.iocoder.yudao.module.system.controller.admin.storage.vo.SystemStorageBlobRespVO;
+import cn.iocoder.yudao.module.system.service.storage.SystemStorageAttachmentService;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.util.ArrayList;
+import java.util.List;
+
+@Service
+public class StorageAttachmentApiImpl implements StorageAttachmentApi {
+
+    @Resource
+    private SystemStorageAttachmentService systemStorageAttachmentService;
+
+    @Override
+    public void bindAttachments(String application, String recordType, Long recordId, List<Long> blobIds) {
+        if (blobIds == null || blobIds.isEmpty()) {
+            return;
+        }
+        SystemStorageAttachmentSaveReqVO reqVO = new SystemStorageAttachmentSaveReqVO();
+        reqVO.setApplication(application != null ? application : "file");
+        reqVO.setRecordType(recordType);
+        reqVO.setRecordId(recordId);
+        List<SystemStorageAttachmentSaveReqVO.BlobItem> blobItems = new ArrayList<>();
+        for (Long blobId : blobIds) {
+            SystemStorageAttachmentSaveReqVO.BlobItem item = new SystemStorageAttachmentSaveReqVO.BlobItem();
+            item.setBlobId(blobId);
+            blobItems.add(item);
+        }
+        reqVO.setBlobItems(blobItems);
+        systemStorageAttachmentService.bindAttachments(reqVO);
+    }
+
+    @Override
+    public List<StorageBlobRespDTO> listAttachments(String recordType, Long recordId) {
+        SystemStorageAttachmentListReqVO reqVO = new SystemStorageAttachmentListReqVO();
+        reqVO.setRecordType(recordType);
+        reqVO.setRecordId(recordId);
+        List<SystemStorageBlobRespVO> list = systemStorageAttachmentService.listAttachments(reqVO);
+        return BeanUtils.toBean(list, StorageBlobRespDTO.class);
+    }
+
+    @Override
+    public void deleteAttachments(List<Long> attachmentIds) {
+        if (attachmentIds == null || attachmentIds.isEmpty()) {
+            return;
+        }
+        systemStorageAttachmentService.deleteAttachments(attachmentIds);
+    }
+
+    @Override
+    public void deleteAttachmentsByRecord(String recordType, Long recordId) {
+        systemStorageAttachmentService.deleteAttachmentsByRecordTypeAndRecordId(recordType, recordId);
+    }
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/dto/StorageBlobRespDTO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/dto/StorageBlobRespDTO.java
new file mode 100644
index 0000000..c0a5724
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/api/storage/dto/StorageBlobRespDTO.java
@@ -0,0 +1,16 @@
+package cn.iocoder.yudao.module.system.api.storage.dto;
+
+import lombok.Data;
+
+@Data
+public class StorageBlobRespDTO {
+
+    private Long id;
+    private String url;
+    private String previewURL;
+    private String name;
+    private String downloadURL;
+    private String application;
+    private Long storageAttachmentId;
+
+}

--
Gitblit v1.9.3