From 7188f28998d47c286e34cbd525986603cfc19ba9 Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期五, 31 七月 2026 17:58:25 +0800
Subject: [PATCH] Merge remote-tracking branch 'origin/dev_business' into dev_business

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java                            |   15 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java                                      |   20 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java                                 |   63 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java                                    |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java                         |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java                                 |   66 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseOrderInStatusConvert.java                                 |   48 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/plan/MesCalPlanDO.java                                    |    8 
 docs/work_order_material_requirement.md                                                                                                 |  257 ++++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/route/MesProRouteProductBomMapper.java                         |    7 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java                                     |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderSaveReqVO.java            |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestRespVO.java           |   31 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java             |    9 
 yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java                                    |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderInStatusEnum.java                                      |   20 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java                           |    4 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java                                 |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomServiceImpl.java                      |    9 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java              |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderRespVO.java               |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java                           |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java                            |    8 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomService.java                          |   10 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseRequestStatusConvert.java                                 |   48 ++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java                                  |  159 +++---
 docs/business_price_field_unification.md                                                                                                |   72 +++
 yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java |   12 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java                               |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java                                |    9 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java                     |   19 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java                       |    6 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java                                    |   37 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java                    |    7 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/dataobject/purchase/ErpPurchaseOrderDO.java                              |   17 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java                             |   52 ++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderItemRespVO.java                   |   48 ++
 yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java                |   15 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java                    |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java                        |    5 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java             |  259 ++++++++--
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java   |    3 
 42 files changed, 1,260 insertions(+), 179 deletions(-)

diff --git a/docs/business_price_field_unification.md b/docs/business_price_field_unification.md
new file mode 100644
index 0000000..099d2a7
--- /dev/null
+++ b/docs/business_price_field_unification.md
@@ -0,0 +1,72 @@
+# 鍟嗘満 price 瀛楁缁熶竴涓� businessPrice 鈥� 鍓嶇鑱旇皟鏂规
+
+## 闂鑳屾櫙
+
+鍓嶇浜у搧缂栬緫琛ㄤ娇鐢� `contractPrice` 浣滀负浠锋牸瀛楁鍙戦�佺粰鍚庣锛屼絾鍚庣浣跨敤鐨勬槸 `businessPrice`锛堝晢鏈轰环鏍硷級銆備袱鑰呮槸鍚屼竴涓蹇碉紝闇�瑕佺粺涓�涓� `businessPrice`銆�
+
+## 娑夊強椤甸潰
+
+- 鍟嗘満鏂板/缂栬緫寮圭獥锛坄src/views/crm/business/modules/form.vue`锛�
+- 浜у搧缂栬緫琛ㄧ粍浠讹紙`src/views/crm/product/components/edit-table.vue`锛�
+- 浜у搧缂栬緫琛ㄥ垪瀹氫箟锛坄src/views/crm/product/components/data.ts`锛�
+
+## API 璇存槑
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| PUT | `/crm/business/update` | 鏇存柊鍟嗘満 |
+| POST | `/crm/business/create` | 鏂板鍟嗘満 |
+
+**璇锋眰浣撳瓧娈靛彉鏇达紙`products[]` 鍐咃級锛�**
+
+| 瀛楁 | 鍙樻洿 | 璇存槑 |
+|------|------|------|
+| `contractPrice` | 鉂� 绉婚櫎 | 鏀逛负 `businessPrice` |
+| `businessPrice` | 鉁� 缁熶竴浣跨敤 | 鍟嗘満浠锋牸 |
+| `totalProductPrice` | 鉁� 宸叉湁 | 浜у搧鎬讳环锛堝悗绔凡鏂板鏀寔锛� |
+| `totalPrice` | 鉁� 宸叉湁 | 鍟嗘満鎬讳环锛堟姌鍚庯級锛堝悗绔凡鏂板鏀寔锛� |
+
+## 鍓嶇淇敼鐐�
+
+### 1. `edit-table.vue` 鈥� 瀛楁鍚� `contractPrice` 鈫� `businessPrice`
+
+鏂囦欢锛歚src/views/crm/product/components/edit-table.vue`
+
+娑夊強 6 澶勶細
+
+| 琛屽彿 | 淇敼 |
+|------|------|
+| 55 | `row.contractPrice` 鈫� `row.businessPrice` |
+| 57 | `row.contractPrice` 鈫� `row.businessPrice` |
+| 63 | `row.contractPrice` 鈫� `row.businessPrice` |
+| 70 | `row.contractPrice` 鈫� `row.businessPrice` |
+| 132 | `contractPrice` 鈫� `businessPrice`锛坱emplate slot name锛� |
+| 134 | `row.contractPrice` 鈫� `row.businessPrice`锛坴-model锛� |
+
+### 2. `data.ts` 鈥� 缂栬緫琛ㄥ垪瀹氫箟
+
+鏂囦欢锛歚src/views/crm/product/components/data.ts`
+
+绗� 91-95 琛岋紝缂栬緫琛紙闈� business 瑙嗗浘锛夌殑鍒楀畾涔夛細
+
+```
+field: 'contractPrice'  鈫�  field: 'businessPrice'
+title: '鍚堝悓浠凤紙鍏冿級'    鈫�  title: '鍟嗘満浠锋牸锛堝厓锛�'
+slots: { default: 'contractPrice' }  鈫�  slots: { default: 'businessPrice' }
+```
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 鏇存柊鍟嗘満鏃朵骇鍝� `businessPrice` 涓� null | 鍚庣鑷姩鐢ㄦ棫鏁版嵁鐨� `businessPrice` merge锛堟棤闇�鍓嶇澶勭悊锛� |
+| 鏂板鍟嗘満鏃� | `businessPrice` 蹇呭~锛屼笉鍙负 null |
+| 浜у搧 `totalPrice` | 鍓嶇鍙洿鎺ヤ紶璁$畻濂界殑鍊硷紝鍚庣浼樺厛浣跨敤锛涙湭浼犳椂鍚庣鐢� `businessPrice * count` 鑷姩璁$畻 |
+| 澶寸骇 `totalProductPrice` / `totalPrice` | 鍓嶇鍙洿鎺ヤ紶锛屽悗绔笉鍐嶈鐩� |
+
+## 娉ㄦ剰浜嬮」
+
+- **涓嶆秹鍙婄殑鏂囦欢**锛歚contract`銆乣statistics`銆乣report`銆乣saleTrace` 涓殑 `contractPrice` 鏄叾浠栦笟鍔℃蹇碉紝涓嶇敤鏀�
+- 浜у搧 `id` 瀛楁鍚庣宸叉柊澧炴敮鎸侊紝鏇存柊鏃朵紶 `id` 鍗冲彲鏍囪瘑宸叉湁浜у搧
+- 鍓嶇 `businessPrice` 鐨勫垵濮嬪寲鍊兼潵鑷� `product.salesPrice`锛堥�変腑浜у搧鏃剁殑榛樿浠凤級
+- 鏇存柊鏃跺彧闇�浼犺淇敼鐨勫瓧娈碉紝鏈紶瀛楁鍚庣鑷姩浠庢棫鏁版嵁缁ф壙
diff --git a/docs/work_order_material_requirement.md b/docs/work_order_material_requirement.md
new file mode 100644
index 0000000..39df8c1
--- /dev/null
+++ b/docs/work_order_material_requirement.md
@@ -0,0 +1,257 @@
+# 宸ュ崟鐗╂枡闇�姹傚垪琛紙鍚簱瀛樻牎楠岋級- 鍓嶇鑱旇皟鏂规
+
+## 娑夊強椤甸潰
+
+- 宸ュ崟璇︽儏椤� / 宸ュ崟鐗╂枡闇�姹� Tab
+- 宸ュ崟鍒楄〃 鈫� 鐗╂枡闇�姹傛煡鐪嬶紙寮圭獥鎴栦晶杈规爮锛�
+- **ERP 閲囪喘璁㈠崟鏂板椤甸潰**锛堝簱瀛樹笉瓒虫椂璺宠浆锛�
+
+## 涓氬姟娴佺▼涓庢暟鎹甫鍏�
+
+1. 宸ュ崟璇︽儏椤� 鈫� 鐗╂枡闇�姹� Tab锛氫紶鍏ュ綋鍓嶅伐鍗� `workOrderId`
+2. 鎺ュ彛鏍规嵁宸ュ崟鐨勬渶鍚庝竴閬撳伐搴忔姇鏂欙紝娌垮伐搴忎骇鍑洪摼鍚戝墠閫掓帹锛屽睍寮�鍑烘墍鏈夊簳灞傚師鏉愭枡鐨勬�婚渶姹傛暟閲�
+3. 闇�姹傛暟閲� = 姣忓崟浣嶆垚鍝佺敤閲� 脳 宸ュ崟鐢熶骇鏁伴噺
+4. 鑱旀煡搴撳瓨杩斿洖鍙敤閲忓拰鍏呰冻鏍囪瘑锛屽墠绔嵁姝ゅ睍绀哄簱瀛樼姸鎬�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| GET | `/mes/pro/work-order-bom/item-list-by-work-order-id` | 鑾峰緱宸ュ崟鐗╂枡闇�姹傚垪琛紙鍚簱瀛橈級 |
+
+**璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| workOrderId | Long | 鏄� | 宸ュ崟缂栧彿 |
+
+**鍝嶅簲瀛楁锛�**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| itemId | Long | 鐗╂枡缂栧彿 |
+| itemCode | String | 鐗╂枡缂栫爜 |
+| itemName | String | 鐗╂枡鍚嶇О |
+| itemSpecification | String | 瑙勬牸鍨嬪彿 |
+| unitMeasureName | String | 鍗曚綅鍚嶇О |
+| quantity | BigDecimal | 闇�姹傛暟閲忥紙宸ュ崟鏁伴噺 脳 BOM 鐢ㄩ噺锛� |
+| stockQuantity | BigDecimal | 鍙敤搴撳瓨锛堝湪搴� - 鍐荤粨 - 鍗犵敤锛� |
+| purchaseOrderQuantity | BigDecimal | 鍦ㄩ�旈噰璐暟锛堟潵婧愪负鏈伐鍗曠殑閲囪喘璁㈠崟鏈叆搴撴暟閲忔眹鎬伙級 |
+| stockSufficient | Boolean | 搴撳瓨鏄惁鍏呰冻锛堝彲鐢ㄥ簱瀛� + 鍦ㄩ�旈噰璐暟 >= 闇�姹傛暟閲忥級锛宼rue=鏄� false=鍚� |
+| itemOrProduct | String | 鐗╂枡/浜у搧鏍囪瘑 |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": [
+    {
+      "itemId": 41,
+      "itemCode": "ITEM_20260716004",
+      "itemName": "閾濇潌",
+      "itemSpecification": "蠁9.5mm",
+      "unitMeasureName": "kg",
+      "quantity": 600.00,
+      "stockQuantity": 500.00,
+      "purchaseOrderQuantity": 200.00,
+      "stockSufficient": true,
+      "itemOrProduct": "ITEM"
+    },
+    {
+      "itemId": 44,
+      "itemCode": "ITEM_20260716008",
+      "itemName": "闀�閿岄挗绾�",
+      "itemSpecification": "蠁1.6mm",
+      "unitMeasureName": "kg",
+      "quantity": 100.00,
+      "stockQuantity": 50.00,
+      "purchaseOrderQuantity": 0.00,
+      "stockSufficient": false,
+      "itemOrProduct": "ITEM"
+    }
+  ]
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| itemCode | 鍒楄〃鍒� | 鐗╂枡缂栫爜 |
+| itemName | 鍒楄〃鍒� | 鐗╂枡鍚嶇О |
+| itemSpecification | 鍒楄〃鍒� | 瑙勬牸鍨嬪彿 |
+| unitMeasureName | 鍒楄〃鍒� | 鍗曚綅 |
+| quantity | 鍒楄〃鍒� | 闇�姹傛暟閲忥紝鍙冲榻愶紝鍗冨垎浣嶆牸寮� |
+| stockQuantity | 鍒楄〃鍒� | 鍙敤搴撳瓨锛屽彸瀵归綈锛屽崈鍒嗕綅鏍煎紡 |
+| purchaseOrderQuantity | 鍒楄〃鍒� | 鍦ㄩ�旈噰璐暟锛屽彸瀵归綈锛屽崈鍒嗕綅鏍煎紡 |
+| stockSufficient | 鍒楄〃鍒� | 搴撳瓨鐘舵�侊紝瑙佷笅鏂圭姸鎬佸睍绀鸿鍒� |
+
+### 搴撳瓨鐘舵�佸睍绀鸿鍒�
+
+| stockSufficient | 灞曠ず鏂囨湰 | 寤鸿棰滆壊 | 璇存槑 |
+|-----------------|---------|---------|------|
+| true | 鍏呰冻 | 缁胯壊 | 鍙敤搴撳瓨 鈮� 闇�姹傛暟閲� |
+| false | 涓嶈冻 | 绾㈣壊 | 鍙敤搴撳瓨 < 闇�姹傛暟閲� |
+
+### 鏁版嵁甯﹀叆鍏崇郴
+
+| 鏉ユ簮 | 甯﹀叆瀛楁 | 璇存槑 |
+|------|----------|------|
+| 宸ュ崟璇︽儏 | workOrderId | 閫氳繃椤甸潰璺敱鍙傛暟鎴栬鏁版嵁鑾峰彇锛屼紶缁欐帴鍙� |
+
+## 鎺ュ彛璋冪敤鏃跺簭
+
+```
+宸ュ崟璇︽儏椤靛姞杞�
+  鈹溾攢鈹� GET /mes/pro/work-order/get?workOrderId=47 鈫� 鑾峰彇宸ュ崟鍩烘湰淇℃伅
+  鈹溾攢鈹� GET /mes/pro/work-order-process/list-by-work-order-id?workOrderId=47 鈫� 鑾峰彇宸ュ簭鍒楄〃
+  鈹斺攢鈹� GET /mes/pro/work-order-bom/item-list-by-work-order-id?workOrderId=47 鈫� 鑾峰彇鐗╂枡闇�姹傦紙鍚簱瀛橈級
+```
+
+鏃犻渶鍓嶇疆璋冪敤锛屼紶鍏� workOrderId 鍗冲彲鐙珛璇锋眰銆�
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 宸ュ崟鏃犲伐搴� | 杩斿洖绌哄垪琛� |
+| 宸ュ崟鏃� BOM 鎶曟枡 | 杩斿洖绌哄垪琛� |
+| 宸ュ崟浜у搧鏈叧鑱斿伐鑹鸿矾绾� | 杩斿洖绌哄垪琛� |
+| 鐗╂枡闇�姹傝绠� | 浠庢渶鍚庝竴閬撳伐搴忥紙sort 鏈�澶э級鐨勬姇鏂欏嚭鍙戯紝娌垮伐搴忎骇鍑洪摼鍚戝墠閫掓帹灞曞紑 |
+| 鏁伴噺璁$畻 | 姣忓崟浣嶆垚鍝佺敤閲� 脳 宸ュ崟鐢熶骇鏁伴噺锛坬uantity锛� |
+| 鍙敤搴撳瓨璁$畻 | 姹囨�绘墍鏈夊簱浣嶇殑锛堝湪搴撴暟閲� - 鍐荤粨鏁伴噺 - 鍗犵敤鏁伴噺锛夛紝鈮�0 鐨勫簱浣嶄笉璁″叆 |
+| 鍦ㄩ�旈噰璐暟璁$畻 | 鏌ヨ鏉ユ簮涓烘湰宸ュ崟锛坰ourceType=1, sourceId=宸ュ崟ID锛夌殑閲囪喘璁㈠崟鏄庣粏锛屾眹鎬伙紙璁㈣喘鏁伴噺 - 宸插叆搴撴暟閲忥級 |
+| 鍏呰冻鍒ゆ柇 | 鍙敤搴撳瓨 + 鍦ㄩ�旈噰璐暟 >= 闇�姹傛暟閲� |
+
+## 閲囪喘鐢宠锛堝簱瀛樹笉瓒虫椂璺宠浆锛�
+
+### 瑙﹀彂鏉′欢
+
+鍒楄〃涓� `stockSufficient = false` 鐨勮锛屽湪鎿嶄綔鍒楀睍绀恒��**鐢宠閲囪喘**銆嶆寜閽��
+
+### 璺宠浆鐩爣
+
+ERP 閲囪喘璁㈠崟鏂板椤甸潰锛歚/erp/purchase-order/create`锛堝墠绔矾鐢憋紝闈炴帴鍙h矾寰勶級
+
+### 鏁版嵁甯﹀叆
+
+浠庣墿鏂欓渶姹傝 + 褰撳墠宸ュ崟鎼哄甫浠ヤ笅鏁版嵁璺宠浆鍒伴噰璐鍗曟柊澧為〉闈細
+
+**宸ュ崟鏉ユ簮瀛楁锛堥噰璐鍗曠嫭鏈夌殑杩芥函瀛楁锛夛細**
+
+| 鏉ユ簮瀛楁 | 鐩爣瀛楁 | 绫诲瀷 | 璇存槑 |
+|----------|----------|------|------|
+| 宸ュ崟 `id` | `sourceId` | Long | 鏉ユ簮鍗曟嵁缂栧彿 |
+| 宸ュ崟 `code` | `sourceNo` | String | 鏉ユ簮鍗曞彿 |
+| 鈥� | `sourceType` | Integer | 鍥哄畾浼� `1`锛堢敓浜у伐鍗曪級 |
+
+**浜у搧琛屽瓧娈碉細**
+
+| 鏉ユ簮瀛楁 | 鐩爣瀛楁 | 璇存槑 |
+|----------|----------|------|
+| `itemId` | `items[].productId` | 浜у搧缂栧彿 |
+| `itemCode` | 鈥� | 浜у搧缂栫爜锛屼緵椤甸潰鏄剧ず |
+| `itemName` | 鈥� | 浜у搧鍚嶇О锛屼緵椤甸潰鏄剧ず |
+| `itemSpecification` | 鈥� | 瑙勬牸鍨嬪彿锛屼緵椤甸潰鏄剧ず |
+| `quantity - stockQuantity` | `items[].count` | 寤鸿閲囪喘鏁伴噺锛堢己鍙� = 闇�姹� - 搴撳瓨锛� |
+| `unitMeasureName` | 鈥� | 鍗曚綅鍚嶇О锛屼緵椤甸潰鏄剧ず |
+
+### 椤甸潰璺宠浆娴佺▼
+
+```
+鐗╂枡闇�姹傚垪琛紙搴撳瓨涓嶈冻琛岋級
+  鈹�
+  鈹溾攢 鐐瑰嚮銆岀敵璇烽噰璐�嶆寜閽�
+  鈹�
+  鈹溾攢 璺宠浆鍒� ERP 閲囪喘璁㈠崟鏂板椤甸潰
+  鈹�     鎼哄甫锛氬伐鍗曟潵婧愪俊鎭紙sourceId銆乻ourceNo銆乻ourceType=1锛�
+  鈹�           浜у搧琛屼俊鎭紙productId銆佹暟閲忋�佺紪鐮併�佸悕绉般�佽鏍笺�佸崟浣嶏級
+  鈹�
+  鈹斺攢 閲囪喘璁㈠崟鏂板椤甸潰
+        鈹溾攢 鑷姩棰勫~鏉ユ簮瀛楁锛堜笉鍙紪杈戯級
+        鈹溾攢 鑷姩棰勫~浜у搧琛岋紙productId + count + unit锛�
+        鈹溾攢 鐢ㄦ埛閫夋嫨渚涘簲鍟嗭紙supplierId锛屽繀濉級
+        鈹溾攢 鐢ㄦ埛琛ュ厖鍗曚环銆佺◣鐜囩瓑淇℃伅
+        鈹斺攢 鎻愪氦 POST /erp/purchase-order/create
+```
+
+### 浜や簰璇存槑
+
+| 鍦烘櫙 | 澶勭悊鏂瑰紡 |
+|------|----------|
+| 鍗曚釜鐗╂枡涓嶈冻 | 鐐瑰嚮璇ヨ銆岀敵璇烽噰璐�嶁啋 璺宠浆閲囪喘璁㈠崟椤甸潰锛岄濉� 1 琛屼骇鍝� |
+| 澶氫釜鐗╂枡涓嶈冻 | 鍙�愯鐐瑰嚮鍒嗗埆鐢宠锛屾垨鏀寔澶氶�夊悗鎵归噺甯﹀埌鍚屼竴涓噰璐鍗� |
+| 鍏ㄩ儴鍏呰冻 | 涓嶅睍绀恒�岀敵璇烽噰璐�嶆寜閽� |
+| 鍙敤搴撳瓨涓� 0 | `stockSufficient = false`锛屽悓鏍峰睍绀恒�岀敵璇烽噰璐�嶏紝缂哄彛 = 鍏ㄩ儴闇�姹傛暟閲� |
+
+### 閲囪喘璁㈠崟鍒涘缓 API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | `/erp/purchase-order/create` | 鍒涘缓閲囪喘璁㈠崟 |
+
+**瀹屾暣璇锋眰鍙傛暟锛�**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 鏉ユ簮 |
+|------|------|------|------|
+| `sourceType` | Integer | 鍚� | 鍥哄畾 `1`锛堢敓浜у伐鍗曪級锛岀敱椤甸潰鎼哄甫 |
+| `sourceNo` | String | 鍚� | 宸ュ崟缂栫爜 `workOrder.code` |
+| `sourceId` | Long | 鍚� | 宸ュ崟 ID `workOrder.id` |
+| `supplierId` | Long | 鏄� | 鐢ㄦ埛鍦ㄩ噰璐〉闈㈤�夋嫨渚涘簲鍟� |
+| `orderTime` | LocalDateTime | 鏄� | 榛樿褰撳ぉ |
+| `items[].productId` | Long | 鏄� | 鐗╂枡闇�姹傜殑 `itemId` |
+| `items[].productUnitId` | Long | 鏄� | 鐗╂枡瀵瑰簲鐨勮閲忓崟浣� ID |
+| `items[].count` | BigDecimal | 鏄� | 缂哄彛鏁伴噺锛堥渶姹� - 搴撳瓨锛� |
+| `items[].productPrice` | BigDecimal | 鍚� | 鐢ㄦ埛鎵嬪伐濉啓 |
+| `items[].taxPercent` | BigDecimal | 鍚� | 鐢ㄦ埛鎵嬪伐濉啓 |
+
+**璇锋眰绀轰緥锛�**
+
+```json
+{
+  "sourceType": 1,
+  "sourceNo": "GD20260730001",
+  "sourceId": 47,
+  "supplierId": 10,
+  "orderTime": "2026-07-30",
+  "items": [
+    {
+      "productId": 41,
+      "productUnitId": 5,
+      "count": 100.00,
+      "productPrice": 12.50,
+      "taxPercent": 13.00
+    }
+  ]
+}
+```
+
+**鍝嶅簲锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "id": 123,
+    "no": "CGD20260730001",
+    "sourceType": 1,
+    "sourceNo": "GD20260730001",
+    "sourceId": 47,
+    "sourceTypeName": "鐢熶骇宸ュ崟",
+    "supplierId": 10,
+    "items": [...]
+  }
+}
+```
+
+## 娉ㄦ剰浜嬮」
+
+- `stockSufficient` 涓� `false` 鏃讹紝鍓嶇搴�**宸紓鍖栧睍绀�**锛堝绾㈣壊楂樹寒锛夛紝骞跺湪鎿嶄綔鍒楁樉绀恒�岀敵璇烽噰璐�嶆寜閽�
+- 鍙敤搴撳瓨涓� 0 鏃讹紝`stockQuantity` 杩斿洖 `0`锛宍stockSufficient` 涓� `false`
+- 鎺ュ彛杩斿洖鐨勭墿鏂欏垪琛ㄥ彲鑳戒负绌猴紙宸ュ崟鏃犲伐搴�/鏃� BOM/鏈叧鑱旇矾绾匡級锛屽墠绔渶澶勭悊绌虹姸鎬佹彁绀�
+- 鏁伴噺瀛楁涓� `BigDecimal`锛屽墠绔睍绀烘椂娉ㄦ剰灏忔暟浣嶅鐞嗭紝寤鸿淇濈暀 2 浣嶅皬鏁�
+- 璺宠浆閲囪喘璁㈠崟鏃讹紝`productUnitId` 闇�瑕侀�氳繃鐗╂枡璇︽儏鎺ュ彛鑾峰彇锛堝綋鍓嶇墿鏂欓渶姹傚垪琛ㄦ湭杩斿洖姝ゅ瓧娈碉級锛屾垨鑰呴噰璐鍗曢〉闈㈡牴鎹� `productId` 鑷鏌ヨ榛樿鍗曚綅
+- 鍚屼竴涓墿鏂欏彲鑳藉湪澶氫釜宸ュ崟涓兘闇�瑕侀噰璐紝閲囪喘璁㈠崟椤甸潰搴旀敮鎸佺敤鎴蜂慨鏀规暟閲�
+- 鏉ユ簮瀛楁 `sourceType`銆乣sourceNo`銆乣sourceId` 鍦ㄩ噰璐鍗曢〉闈㈠睍绀轰负**鍙**锛屼笉鍏佽鐢ㄦ埛淇敼
+- 閲囪喘璁㈠崟璇︽儏/鍒楄〃涓彲閫氳繃 `sourceTypeName`锛堝"鐢熶骇宸ュ崟"锛夊拰 `sourceNo`锛堝伐鍗曠紪鐮侊級杩芥函鍒版潵婧愬伐鍗�
diff --git a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
index 3442060..2dad8f1 100644
--- a/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
+++ b/yudao-framework/yudao-common/src/main/java/cn/iocoder/yudao/framework/common/util/json/databind/TimestampLocalDateTimeDeserializer.java
@@ -7,6 +7,7 @@
 import tools.jackson.databind.ValueDeserializer;
 
 import java.time.Instant;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.time.format.DateTimeFormatter;
@@ -20,7 +21,8 @@
 
     public static final TimestampLocalDateTimeDeserializer INSTANCE = new TimestampLocalDateTimeDeserializer();
 
-    private static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
+    private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
 
     @Override
     public LocalDateTime deserialize(JsonParser p, DeserializationContext ctxt) throws JacksonException {
@@ -34,9 +36,13 @@
             long millis = Long.parseLong(text);
             return LocalDateTime.ofInstant(Instant.ofEpochMilli(millis), ZoneId.systemDefault());
         } catch (NumberFormatException e) {
-            // 涓嶆槸绾暟瀛楋紝鎸夐粯璁ゆ棩鏈熸牸寮忚В鏋�
+            // 涓嶆槸绾暟瀛楋紝鎸夋棩鏈熸牸寮忚В鏋�
         }
-        return LocalDateTime.parse(text, DEFAULT_FORMATTER);
+        // 绾棩鏈熸牸寮忥紙濡� "2026-07-29"锛夛紝琛ュ叏涓哄綋澶� 00:00:00
+        if (text.length() == 10) {
+            return LocalDate.parse(text, DATE_FORMATTER).atStartOfDay();
+        }
+        return LocalDateTime.parse(text, DATE_TIME_FORMATTER);
     }
 
 }
diff --git a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
index f4f17ec..b9ec1ed 100644
--- a/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
+++ b/yudao-framework/yudao-spring-boot-starter-excel/src/main/java/cn/iocoder/yudao/framework/excel/core/util/ExcelUtils.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.framework.excel.core.util;
 
+import cn.idev.excel.ExcelWriter;
 import cn.idev.excel.FastExcelFactory;
 import cn.idev.excel.converters.longconverter.LongStringConverter;
 import cn.iocoder.yudao.framework.common.util.http.HttpUtils;
@@ -33,12 +34,22 @@
     public static <T> void write(HttpServletResponse response, String filename, String sheetName,
                                  Class<T> head, List<T> data) throws IOException {
         // 杈撳嚭 Excel
-        FastExcelFactory.write(response.getOutputStream(), head)
+        ExcelWriter excelWriter = FastExcelFactory.write(response.getOutputStream(), head)
                 .autoCloseStream(false) // 涓嶈鑷姩鍏抽棴锛屼氦缁� Servlet 鑷繁澶勭悊
                 .registerWriteHandler(new ColumnWidthMatchStyleStrategy()) // 鍩轰簬 column 闀垮害锛岃嚜鍔ㄩ�傞厤銆傛渶澶� 255 瀹藉害
                 .registerWriteHandler(new SelectSheetWriteHandler(head)) // 鍩轰簬鍥哄畾 sheet 瀹炵幇涓嬫媺妗�
                 .registerConverter(new LongStringConverter()) // 閬垮厤 Long 绫诲瀷涓㈠け绮惧害
-                .sheet(sheetName).doWrite(data);
+                .build();
+        try {
+            excelWriter.write(data, FastExcelFactory.writerSheet(sheetName).build());
+        } finally {
+            // finish() 灏� workbook 鍐欏叆鍒� response 杈撳嚭娴�
+            excelWriter.finish();
+            // close() 瑙﹀彂 dispose() 娓呯悊涓存椂鏂囦欢锛�
+            // POI SXSSFWorkbook.dispose() 鍙兘杈撳嚭 WARN "Failed to dispose sheet"锛�
+            // 杩欐槸 POI 宸茬煡闂锛堜复鏃舵枃浠舵竻鐞嗘椂 BufferedWriter 宸插叧闂級锛屼笉褰卞搷瀵煎嚭缁撴灉
+            excelWriter.close();
+        }
         // 璁剧疆 header 鍜� contentType銆傚啓鍦ㄦ渶鍚庣殑鍘熷洜鏄紝閬垮厤鎶ラ敊鏃讹紝鍝嶅簲 contentType 宸茬粡琚慨鏀逛簡
         response.addHeader("Content-Disposition", "attachment;filename=" + HttpUtils.encodeUtf8(filename));
         response.setContentType("application/vnd.ms-excel;charset=UTF-8");
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
index 13ab5bc..53059be 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/controller/admin/business/vo/business/CrmBusinessSaveReqVO.java
@@ -58,6 +58,12 @@
     @NotNull(message = "鏁村崟鎶樻墸涓嶈兘涓虹┖")
     private BigDecimal discountPercent;
 
+    @Schema(description = "浜у搧鎬讳环", example = "12000.00")
+    private BigDecimal totalProductPrice;
+
+    @Schema(description = "鍟嗘満鎬讳环锛堟姌鍚庯級", example = "11280.00")
+    private BigDecimal totalPrice;
+
     @Schema(description = "澶囨敞", example = "闅忎究")
     @DiffLogField(name = "澶囨敞")
     private String remark;
@@ -73,6 +79,9 @@
     @NoArgsConstructor
     @AllArgsConstructor
     public static class BusinessProduct {
+
+        @Schema(description = "浜у搧鍏宠仈缂栧彿", example = "5")
+        private Long id;
 
         @Schema(description = "MDM 鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "20529")
         @NotNull(message = "鐗╂枡缂栧彿涓嶈兘涓虹┖")
@@ -94,6 +103,9 @@
         @NotNull(message = "鏁伴噺涓嶈兘涓虹┖")
         private BigDecimal count;
 
+        @Schema(description = "鎬昏浠锋牸锛坆usinessPrice * count锛�", example = "12000.00")
+        private BigDecimal totalPrice;
+
     }
 
 }
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
index 263b383..0aaa4ec 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/service/business/CrmBusinessServiceImpl.java
@@ -134,8 +134,9 @@
         updateReqVO.setOwnerUserId(null).setStatusTypeId(null); // 涓嶅厑璁告洿鏂扮殑瀛楁
         // 1.1 鏍¢獙瀛樺湪
         CrmBusinessDO oldBusiness = validateBusinessExists(updateReqVO.getId());
-        // 1.2 鏍¢獙浜у搧椤圭殑鏈夋晥鎬�
-        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts());
+        // 1.2 鏍¢獙浜у搧椤圭殑鏈夋晥鎬э紙浼犲叆鏃т骇鍝佸垪琛ㄧ敤浜庡悎骞� null 瀛楁锛�
+        List<CrmBusinessProductDO> oldProducts = businessProductMapper.selectListByBusinessId(updateReqVO.getId());
+        List<CrmBusinessProductDO> businessProducts = validateBusinessProducts(updateReqVO.getProducts(), oldProducts);
         // 1.3 鏍¢獙鍏宠仈瀛楁
         validateRelationDataExists(updateReqVO);
 
@@ -147,7 +148,7 @@
         updateBusinessProduct(updateObj.getId(), businessProducts);
 
         // 3. 璁板綍鎿嶄綔鏃ュ織涓婁笅鏂�
-        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 閬垮厤鎿嶄綔鏃ュ織鍑虹幇鈥滃垹闄よ礋璐d汉鈥濈殑鎯呭喌
+        updateReqVO.setOwnerUserId(oldBusiness.getOwnerUserId()); // 閬垮厤鎿嶄綔鏃ュ織鍑虹幇"鍒犻櫎璐熻矗浜�"鐨勬儏鍐�
         LogRecordContext.putVariable(DiffParseFunction.OLD_OBJECT, BeanUtils.toBean(oldBusiness, CrmBusinessSaveReqVO.class));
         LogRecordContext.putVariable("businessName", oldBusiness.getName());
     }
@@ -216,6 +217,18 @@
     }
 
     private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list) {
+        return validateBusinessProducts(list, Collections.emptyList());
+    }
+
+    /**
+     * 鏍¢獙浜у搧椤圭殑鏈夋晥鎬э紙鏇存柊鍦烘櫙鍚堝苟鏃ф暟鎹級
+     *
+     * @param list       鍓嶇浼犲叆鐨勪骇鍝佸垪琛�
+     * @param oldProducts 宸叉湁浜у搧鍒楄〃锛堢敤浜庡悎骞� null 瀛楁锛�
+     * @return 澶勭悊鍚庣殑浜у搧 DO 鍒楄〃
+     */
+    private List<CrmBusinessProductDO> validateBusinessProducts(List<CrmBusinessSaveReqVO.BusinessProduct> list,
+                                                                  List<CrmBusinessProductDO> oldProducts) {
         // 1. 鏍¢獙鐗╂枡瀛樺湪
         Set<Long> itemIds = convertSet(list, CrmBusinessSaveReqVO.BusinessProduct::getItemId);
         Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(itemIds);
@@ -224,15 +237,47 @@
                 throw exception(PRODUCT_NOT_EXISTS);
             }
         }
-        // 2. 杞寲涓� CrmBusinessProductDO 鍒楄〃
-        return convertList(list, o -> BeanUtils.toBean(o, CrmBusinessProductDO.class,
-                item -> item.setTotalPrice(MoneyUtils.priceMultiply(item.getBusinessPrice(), item.getCount()))));
+        // 2. 鏋勫缓鏃т骇鍝� Map锛堟寜 id 绱㈠紩锛�
+        Map<Long, CrmBusinessProductDO> oldProductMap = convertMap(oldProducts, CrmBusinessProductDO::getId);
+        // 3. 杞寲涓� CrmBusinessProductDO 鍒楄〃锛堝悎骞舵棫鏁版嵁锛�
+        return convertList(list, o -> {
+            CrmBusinessProductDO product = BeanUtils.toBean(o, CrmBusinessProductDO.class);
+            // 鏇存柊鍦烘櫙锛氬鏋滀骇鍝佸凡鏈� id 涓旀湁鏃ф暟鎹紝鍒欏悎骞� null 瀛楁
+            if (o.getId() != null && oldProductMap.containsKey(o.getId())) {
+                CrmBusinessProductDO old = oldProductMap.get(o.getId());
+                if (product.getBusinessPrice() == null) {
+                    product.setBusinessPrice(old.getBusinessPrice());
+                }
+                if (product.getItemPrice() == null) {
+                    product.setItemPrice(old.getItemPrice());
+                }
+                if (product.getCount() == null) {
+                    product.setCount(old.getCount());
+                }
+                if (product.getItemUnitId() == null) {
+                    product.setItemUnitId(old.getItemUnitId());
+                }
+            }
+            // 浼樺厛浣跨敤鍓嶇浼犲叆鐨� totalPrice锛屽惁鍒欓�氳繃 businessPrice * count 璁$畻
+            if (o.getTotalPrice() != null) {
+                product.setTotalPrice(o.getTotalPrice());
+            } else {
+                product.setTotalPrice(MoneyUtils.priceMultiply(product.getBusinessPrice(), product.getCount()));
+            }
+            return product;
+        });
     }
 
     private void calculateTotalPrice(CrmBusinessDO business, List<CrmBusinessProductDO> businessProducts) {
-        business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
-        BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
-        business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
+        // totalProductPrice锛氫紭鍏堜娇鐢ㄥ墠绔紶鍏ュ�硷紝鍚﹀垯浠庝骇鍝佹眹鎬�
+        if (business.getTotalProductPrice() == null) {
+            business.setTotalProductPrice(getSumValue(businessProducts, CrmBusinessProductDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
+        }
+        // totalPrice锛氫紭鍏堜娇鐢ㄥ墠绔紶鍏ュ�硷紝鍚﹀垯鎸夋姌鎵h绠�
+        if (business.getTotalPrice() == null) {
+            BigDecimal discountPrice = MoneyUtils.priceMultiplyPercent(business.getTotalProductPrice(), business.getDiscountPercent());
+            business.setTotalPrice(business.getTotalProductPrice().subtract(discountPrice));
+        }
     }
 
     @Override
diff --git a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
index f62d3d1..98e1b6a 100644
--- a/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
+++ b/yudao-module-erp-api/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApi.java
@@ -84,4 +84,13 @@
      */
     CommonResult<Long> createPurchaseOrder(ErpPurchaseOrderCreateReqDTO createReqDTO);
 
+    /**
+     * 鎸夋潵婧愭煡璇㈤噰璐鍗曟槑缁嗗垪琛紙鐢ㄤ簬杩芥函鏉ユ簮宸ュ崟鐨勯噰璐儏鍐碉級
+     *
+     * @param sourceType 鏉ユ簮绫诲瀷
+     * @param sourceId   鏉ユ簮鍗曟嵁缂栧彿
+     * @return 閲囪喘璁㈠崟鏄庣粏鍒楄〃
+     */
+    CommonResult<List<ErpPurchaseOrderItemRespDTO>> getPurchaseOrderItemListBySource(Integer sourceType, Long sourceId);
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
index 3b1cedf..7daf6eb 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/api/purchase/ErpPurchaseOrderApiImpl.java
@@ -118,4 +118,20 @@
         return success(purchaseOrderService.createPurchaseOrder(saveReqVO));
     }
 
+    @Override
+    public CommonResult<List<ErpPurchaseOrderItemRespDTO>> getPurchaseOrderItemListBySource(Integer sourceType, Long sourceId) {
+        if (sourceType == null || sourceId == null) {
+            return success(Collections.emptyList());
+        }
+        // 1. 鎸夋潵婧愭煡璇㈤噰璐鍗�
+        List<ErpPurchaseOrderDO> orders = purchaseOrderService.getPurchaseOrderListBySource(sourceType, sourceId);
+        if (orders.isEmpty()) {
+            return success(Collections.emptyList());
+        }
+        // 2. 鏌ヨ鎵�鏈夎鍗曠殑鏄庣粏
+        List<Long> orderIds = orders.stream().map(ErpPurchaseOrderDO::getId).toList();
+        List<ErpPurchaseOrderItemDO> items = purchaseOrderItemMapper.selectListByOrderIds(orderIds);
+        return success(BeanUtils.toBean(items, ErpPurchaseOrderItemRespDTO.class));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
index 612761d..70128e8 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -13,6 +13,7 @@
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderSourceTypeEnum;
 import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
 import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
 import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
@@ -128,7 +129,9 @@
                     item.setStockCount(BigDecimal.ZERO);
                     MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                             .setProductBarCode(mdmItem.getBarCode()));
-                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_order", id))));
+                })).setAttachmentList(storageAttachmentApi.listAttachments("erp_purchase_order", id))
+                        .setSourceTypeName(ErpPurchaseOrderSourceTypeEnum.WORK_ORDER.getType()
+                                .equals(purchaseOrder.getSourceType()) ? "鐢熶骇宸ュ崟" : null)));
     }
 
     @GetMapping("/page")
@@ -185,6 +188,8 @@
             purchaseOrder.setProductNames(CollUtil.join(purchaseOrder.getItems(), "锛�", ErpPurchaseOrderRespVO.Item::getProductName));
             MapUtils.findAndThen(supplierMap, purchaseOrder.getSupplierId(), supplier -> purchaseOrder.setSupplierName(supplier.getName()));
             MapUtils.findAndThen(userMap, Long.parseLong(purchaseOrder.getCreator()), user -> purchaseOrder.setCreatorName(user.getNickname()));
+            purchaseOrder.setSourceTypeName(ErpPurchaseOrderSourceTypeEnum.WORK_ORDER.getType()
+                    .equals(purchaseOrder.getSourceType()) ? "鐢熶骇宸ュ崟" : null);
         });
     }
 
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 51cdf18..448bd6e 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
@@ -64,6 +64,20 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", requiredMode = Schema.RequiredMode.REQUIRED, example = "7127")
     private BigDecimal depositPrice;
 
+    @Schema(description = "鏉ユ簮绫诲瀷", example = "1")
+    @ExcelProperty("鏉ユ簮绫诲瀷")
+    private Integer sourceType;
+
+    @Schema(description = "鏉ユ簮鍗曞彿", example = "GD20260730001")
+    @ExcelProperty("鏉ユ簮鍗曞彿")
+    private String sourceNo;
+
+    @Schema(description = "鏉ユ簮鍗曟嵁缂栧彿", example = "48")
+    private Long sourceId;
+
+    @Schema(description = "鏉ユ簮绫诲瀷鍚嶇О", example = "鐢熶骇宸ュ崟")
+    private String sourceTypeName;
+
     @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 201068d..4ee92f5 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,6 +32,15 @@
     @Schema(description = "瀹氶噾閲戦锛屽崟浣嶏細鍏�", example = "7127")
     private BigDecimal depositPrice;
 
+    @Schema(description = "鏉ユ簮绫诲瀷", example = "1")
+    private Integer sourceType;
+
+    @Schema(description = "鏉ユ簮鍗曞彿", example = "GD20260730001")
+    private String sourceNo;
+
+    @Schema(description = "鏉ユ簮鍗曟嵁缂栧彿", example = "48")
+    private Long sourceId;
+
     @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 3debc10..0ebadb5 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
@@ -1,5 +1,8 @@
 package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.request;
 
+import cn.idev.excel.annotation.ExcelIgnore;
+import cn.idev.excel.annotation.ExcelIgnoreUnannotated;
+import cn.idev.excel.annotation.ExcelProperty;
 import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
@@ -11,75 +14,95 @@
 
 @Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠 Response VO")
 @Data
+@ExcelIgnoreUnannotated
 public class ErpPurchaseRequestRespVO {
 
     @Schema(description = "缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelIgnore
     private Long id;
 
     @Schema(description = "閲囪喘鐢宠鍗曞彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "CGSQ001")
+    @ExcelProperty("閲囪喘鐢宠鍗曞彿")
     private String no;
 
     @Schema(description = "鐘舵��", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @ExcelProperty(value = "鐘舵��", converter = cn.iocoder.yudao.module.erp.convert.ErpPurchaseRequestStatusConvert.class)
     private Integer status;
 
-    @Schema(description = "宸ヤ綔娴佺紪鍙�", example = "1043")
-    private String processInstanceId;
-
     @Schema(description = "鐢宠浜虹紪鍙�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @ExcelProperty("鐢宠浜虹紪鍙�")
     private Long requestUserId;
 
     @Schema(description = "鐢宠浜哄鍚�", example = "寮犱笁")
+    @ExcelProperty("鐢宠浜哄鍚�")
     private String requestUserName;
 
     @Schema(description = "鐢宠閮ㄩ棬缂栧彿", example = "1")
+    @ExcelProperty("鐢宠閮ㄩ棬缂栧彿")
     private Long requestDeptId;
 
     @Schema(description = "鐢宠閮ㄩ棬鍚嶇О", example = "鐢熶骇閮�")
+    @ExcelProperty("鐢宠閮ㄩ棬鍚嶇О")
     private String requestDeptName;
 
     @Schema(description = "鐢宠鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鐢宠鏃堕棿")
     private LocalDateTime requestTime;
 
     @Schema(description = "鐢宠鐞嗙敱", example = "鐢熶骇闇�瑕�")
+    @ExcelProperty("鐢宠鐞嗙敱")
     private String requestReason;
 
     @Schema(description = "渚涘簲鍟嗙紪鍙�", example = "1")
+    @ExcelProperty("渚涘簲鍟嗙紪鍙�")
     private Long supplierId;
 
     @Schema(description = "渚涘簲鍟嗗悕绉�", example = "渚涘簲鍟咥")
+    @ExcelProperty("渚涘簲鍟嗗悕绉�")
     private String supplierName;
 
     @Schema(description = "瀹氶噾閲戦", example = "1000.00")
+    @ExcelProperty("瀹氶噾閲戦")
     private BigDecimal depositPrice;
 
     @Schema(description = "鍚堣鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍚堣鏁伴噺")
     private BigDecimal totalCount;
 
     @Schema(description = "鍚堣浠锋牸", requiredMode = Schema.RequiredMode.REQUIRED)
+    @ExcelProperty("鍚堣浠锋牸")
     private BigDecimal totalPrice;
 
     @Schema(description = "浜у搧鍚嶇О鍒楄〃", example = "浜у搧A,浜у搧B")
+    @ExcelProperty("浜у搧鍚嶇О鍒楄〃")
     private String productNames;
 
     @Schema(description = "鐢熸垚鐨勯噰璐鍗曠紪鍙�", example = "1")
+    @ExcelIgnore
     private Long orderId;
 
     @Schema(description = "鐢熸垚鐨勯噰璐鍗曞彿", example = "CGDD001")
+    @ExcelProperty("閲囪喘璁㈠崟鍙�")
     private String orderNo;
 
-    @Schema(description = "鍏ュ簱鐘舵�侊紙0:鏈叆搴� 1:閮ㄥ垎鍏ュ簱 2:鍏ㄩ儴鍏ュ簱锛�", example = "0")
+    @Schema(description = "鍏ュ簱鐘舵��", example = "0")
+    @ExcelProperty(value = "鍏ュ簱鐘舵��", converter = cn.iocoder.yudao.module.erp.convert.ErpPurchaseOrderInStatusConvert.class)
     private Integer inStatus;
 
     @Schema(description = "澶囨敞", example = "澶囨敞")
+    @ExcelProperty("澶囨敞")
     private String remark;
 
     @Schema(description = "闄勪欢鍒楄〃")
+    @ExcelIgnore
     private List<StorageBlobRespDTO> attachmentList;
 
     @Schema(description = "鍒涘缓鏃堕棿")
+    @ExcelProperty("鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
 
     @Schema(description = "鐢宠鏄庣粏鍒楄〃")
+    @ExcelIgnore
     private java.util.List<Item> items;
 
     @Schema(description = "鐢宠鏄庣粏椤�")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
index a96501b..3e2adf4 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java
@@ -10,6 +10,7 @@
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
 import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderItemRespVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderRespVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
@@ -119,6 +120,20 @@
         return success(true);
     }
 
+    @GetMapping("/item/list-by-order-id")
+    @Operation(summary = "鏌ヨ閿�鍞鍗曠殑鐗╂枡鏄庣粏鍒楄〃锛堢敤浜庡彂璐ч�氱煡鍗曞垱寤猴級")
+    @Parameter(name = "orderId", description = "閿�鍞鍗曠紪鍙�", required = true, example = "1")
+    @PreAuthorize("@ss.hasPermission('erp:sale-order:query')")
+    public CommonResult<List<ErpSaleOrderItemRespVO>> getSaleOrderItemListByOrderId(@RequestParam("orderId") Long orderId) {
+        List<ErpSaleOrderItemDO> itemList = saleOrderService.getSaleOrderItemListByOrderId(orderId);
+        Map<Long, MdmItemRespDTO> itemMap = mdmItemApi.getItemMap(
+                convertSet(itemList, ErpSaleOrderItemDO::getProductId));
+        return success(BeanUtils.toBean(itemList, ErpSaleOrderItemRespVO.class, vo -> {
+            MapUtils.findAndThen(itemMap, vo.getProductId(), item ->
+                    vo.setProductName(item.getName()).setProductBarCode(item.getBarCode()));
+        }));
+    }
+
     @GetMapping("/get")
     @Operation(summary = "鑾峰緱閿�鍞鍗�")
     @Parameter(name = "id", description = "缂栧彿", required = true, example = "1024")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderItemRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderItemRespVO.java
new file mode 100644
index 0000000..7ca8f8f
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderItemRespVO.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+/**
+ * ERP 閿�鍞鍗曢」 Response VO锛堜緵鍓嶇鍙戣揣閫氱煡鍗曞垱寤烘椂鏌ヨ璁㈠崟鏄庣粏锛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - ERP 閿�鍞鍗曢」 Response VO")
+@Data
+public class ErpSaleOrderItemRespVO {
+
+    @Schema(description = "缂栧彿锛堥攢鍞鍗曡ID锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "閿�鍞鍗曠紪鍙�", example = "1")
+    private Long orderId;
+
+    @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long productId;
+
+    @Schema(description = "鐗╂枡鍚嶇О", example = "浜у搧A")
+    private String productName;
+
+    @Schema(description = "鐗╂枡鏉$爜", example = "12345678")
+    private String productBarCode;
+
+    @Schema(description = "璁¢噺鍗曚綅缂栧彿", example = "1")
+    private Long productUnitId;
+
+    @Schema(description = "璁¢噺鍗曚綅鍚嶇О", example = "涓�")
+    private String productUnitName;
+
+    @Schema(description = "浜у搧鍗曚环", example = "100.00")
+    private BigDecimal productPrice;
+
+    @Schema(description = "鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
+    private BigDecimal count;
+
+    @Schema(description = "鎬讳环", example = "1000.00")
+    private BigDecimal totalPrice;
+
+    @Schema(description = "澶囨敞", example = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseOrderInStatusConvert.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseOrderInStatusConvert.java
new file mode 100644
index 0000000..d5cf42c
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseOrderInStatusConvert.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.erp.convert;
+
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
+
+/**
+ * ERP 閲囪喘鍏ュ簱鐘舵�� Excel 杞崲鍣�
+ * <p>
+ * 灏� Integer 鍏ュ簱鐘舵�佺爜杞崲涓轰腑鏂囧悕绉�
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public class ErpPurchaseOrderInStatusConvert implements Converter<Integer> {
+
+    @Override
+    public Class<?> supportJavaTypeKey() {
+        return Integer.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty,
+                                               GlobalConfiguration globalConfiguration) {
+        if (value == null) {
+            return new WriteCellData<>("");
+        }
+        ErpPurchaseOrderInStatusEnum statusEnum = ErpPurchaseOrderInStatusEnum.fromStatus(value);
+        return new WriteCellData<>(statusEnum != null ? statusEnum.getName() : String.valueOf(value));
+    }
+
+    @Override
+    public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
+                                     GlobalConfiguration globalConfiguration) {
+        String label = cellData.getStringValue();
+        ErpPurchaseOrderInStatusEnum statusEnum = ErpPurchaseOrderInStatusEnum.fromName(label);
+        return statusEnum != null ? statusEnum.getStatus() : null;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseRequestStatusConvert.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseRequestStatusConvert.java
new file mode 100644
index 0000000..eb1a32e
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/convert/ErpPurchaseRequestStatusConvert.java
@@ -0,0 +1,48 @@
+package cn.iocoder.yudao.module.erp.convert;
+
+import cn.idev.excel.converters.Converter;
+import cn.idev.excel.enums.CellDataTypeEnum;
+import cn.idev.excel.metadata.GlobalConfiguration;
+import cn.idev.excel.metadata.data.ReadCellData;
+import cn.idev.excel.metadata.data.WriteCellData;
+import cn.idev.excel.metadata.property.ExcelContentProperty;
+import cn.iocoder.yudao.module.erp.enums.ErpPurchaseRequestStatusEnum;
+
+/**
+ * ERP 閲囪喘鐢宠鐘舵�� Excel 杞崲鍣�
+ * <p>
+ * 灏� Integer 鐘舵�佺爜杞崲涓轰腑鏂囧悕绉帮紝鐢ㄤ簬 Excel 瀵煎嚭
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+public class ErpPurchaseRequestStatusConvert implements Converter<Integer> {
+
+    @Override
+    public Class<?> supportJavaTypeKey() {
+        return Integer.class;
+    }
+
+    @Override
+    public CellDataTypeEnum supportExcelTypeKey() {
+        return CellDataTypeEnum.STRING;
+    }
+
+    @Override
+    public WriteCellData<?> convertToExcelData(Integer value, ExcelContentProperty contentProperty,
+                                               GlobalConfiguration globalConfiguration) {
+        if (value == null) {
+            return new WriteCellData<>("");
+        }
+        ErpPurchaseRequestStatusEnum statusEnum = ErpPurchaseRequestStatusEnum.fromStatus(value);
+        return new WriteCellData<>(statusEnum != null ? statusEnum.getName() : String.valueOf(value));
+    }
+
+    @Override
+    public Integer convertToJavaData(ReadCellData<?> cellData, ExcelContentProperty contentProperty,
+                                     GlobalConfiguration globalConfiguration) {
+        String label = cellData.getStringValue();
+        ErpPurchaseRequestStatusEnum statusEnum = ErpPurchaseRequestStatusEnum.fromName(label);
+        return statusEnum != null ? statusEnum.getStatus() : null;
+    }
+
+}
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 48266e3..32f6244 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,6 +92,23 @@
     private BigDecimal depositPrice;
 
     /**
+     * 鏉ユ簮绫诲瀷
+     *
+     * 鏋氫妇 {@link cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderSourceTypeEnum}
+     */
+    private Integer sourceType;
+    /**
+     * 鏉ユ簮鍗曞彿
+     */
+    private String sourceNo;
+    /**
+     * 鏉ユ簮鍗曟嵁缂栧彿
+     *
+     * 鍏宠仈鏉ユ簮鍗曟嵁琛ㄧ殑涓婚敭 ID
+     */
+    private Long sourceId;
+
+    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
index 76f979c..dc5c147 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -81,4 +81,13 @@
                 .orderByDesc(ErpPurchaseOrderDO::getId));
     }
 
+    /**
+     * 鎸夋潵婧愭煡璇㈤噰璐鍗曪紙鐢ㄤ簬杩芥函鏉ユ簮宸ュ崟瀵瑰簲鐨勯噰璐鍗曪級
+     */
+    default List<ErpPurchaseOrderDO> selectListBySource(Integer sourceType, Long sourceId) {
+        return selectList(new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
+                .eq(ErpPurchaseOrderDO::getSourceType, sourceType)
+                .eq(ErpPurchaseOrderDO::getSourceId, sourceId));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderInStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderInStatusEnum.java
index c1e5a9a..f56a937 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderInStatusEnum.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderInStatusEnum.java
@@ -30,4 +30,24 @@
         return ARRAYS;
     }
 
+    /**
+     * 鏍规嵁鐘舵�佺爜鑾峰彇鏋氫妇
+     */
+    public static ErpPurchaseOrderInStatusEnum fromStatus(Integer status) {
+        return Arrays.stream(values())
+                .filter(e -> e.getStatus().equals(status))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 鏍规嵁鍚嶇О鑾峰彇鏋氫妇
+     */
+    public static ErpPurchaseOrderInStatusEnum fromName(String name) {
+        return Arrays.stream(values())
+                .filter(e -> e.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java
new file mode 100644
index 0000000..5c7eb9b
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseOrderSourceTypeEnum.java
@@ -0,0 +1,37 @@
+package cn.iocoder.yudao.module.erp.enums;
+
+import cn.iocoder.yudao.framework.common.core.ArrayValuable;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * ERP 閲囪喘璁㈠崟鏉ユ簮绫诲瀷鏋氫妇
+ *
+ * @author 鑺嬮亾婧愮爜
+ */
+@RequiredArgsConstructor
+@Getter
+public enum ErpPurchaseOrderSourceTypeEnum implements ArrayValuable<Integer> {
+
+    WORK_ORDER(1, "鐢熶骇宸ュ崟");
+
+    public static final Integer[] ARRAYS = Arrays.stream(values())
+            .map(ErpPurchaseOrderSourceTypeEnum::getType).toArray(Integer[]::new);
+
+    /**
+     * 绫诲瀷鍊�
+     */
+    private final Integer type;
+    /**
+     * 绫诲瀷鍚�
+     */
+    private final String name;
+
+    @Override
+    public Integer[] array() {
+        return ARRAYS;
+    }
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
index 6eb1ad3..ad9493f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpPurchaseRequestStatusEnum.java
@@ -35,4 +35,24 @@
         return ARRAYS;
     }
 
+    /**
+     * 鏍规嵁鐘舵�佺爜鑾峰彇鏋氫妇
+     */
+    public static ErpPurchaseRequestStatusEnum fromStatus(Integer status) {
+        return Arrays.stream(values())
+                .filter(e -> e.getStatus().equals(status))
+                .findFirst()
+                .orElse(null);
+    }
+
+    /**
+     * 鏍规嵁鍚嶇О鑾峰彇鏋氫妇
+     */
+    public static ErpPurchaseRequestStatusEnum fromName(String name) {
+        return Arrays.stream(values())
+                .filter(e -> e.getName().equals(name))
+                .findFirst()
+                .orElse(null);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
index a7b6fdd..af4ced2 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java
@@ -149,6 +149,15 @@
     List<ErpPurchaseOrderDO> getPurchaseOrderList(List<Long> ids);
 
     /**
+     * 鎸夋潵婧愭煡璇㈤噰璐鍗曞垪琛�
+     *
+     * @param sourceType 鏉ユ簮绫诲瀷
+     * @param sourceId   鏉ユ簮鍗曟嵁缂栧彿
+     * @return 閲囪喘璁㈠崟鍒楄〃
+     */
+    List<ErpPurchaseOrderDO> getPurchaseOrderListBySource(Integer sourceType, Long sourceId);
+
+    /**
      * 鏇存柊閲囪喘璁㈠崟鐨勫叆搴撶姸鎬�
      *
      * 鏍规嵁鏄庣粏鍏ュ簱鏁伴噺鑷姩璁$畻璁㈠崟鐘舵�侊細
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
index cd140a7..e04714b 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java
@@ -451,6 +451,14 @@
     }
 
     @Override
+    public List<ErpPurchaseOrderDO> getPurchaseOrderListBySource(Integer sourceType, Long sourceId) {
+        if (sourceType == null || sourceId == null) {
+            return Collections.emptyList();
+        }
+        return purchaseOrderMapper.selectListBySource(sourceType, sourceId);
+    }
+
+    @Override
     public void updatePurchaseOrderInStatus(Long orderId) {
         // 1. 鑾峰彇閲囪喘璁㈠崟
         ErpPurchaseOrderDO order = validatePurchaseOrderExists(orderId);
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
index 243900d..90a8d45 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -94,14 +94,14 @@
 
     @Schema(description = "鏁欒偛缁忓巻鍒楄〃")
     @Valid
-    private List<HrmEmployeeEducationSaveReqVO> educationList;
+    private List<HrmEmployeeEducationSaveReqVO> educations;
 
     @Schema(description = "宸ヤ綔缁忓巻鍒楄〃")
     @Valid
-    private List<HrmEmployeeWorkHistorySaveReqVO> workHistoryList;
+    private List<HrmEmployeeWorkHistorySaveReqVO> workHistories;
 
     @Schema(description = "绱ф�ヨ仈绯讳汉鍒楄〃")
     @Valid
-    private List<HrmEmployeeEmergencyContactSaveReqVO> emergencyContactList;
+    private List<HrmEmployeeEmergencyContactSaveReqVO> emergencyContacts;
 
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java
index 35aea9b..d9390eb 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEducationMapper.java
@@ -19,4 +19,8 @@
         return selectList(HrmEmployeeEducationDO::getEmployeeId, employeeId);
     }
 
+    default void deleteByEmployeeId(Long employeeId) {
+        delete(HrmEmployeeEducationDO::getEmployeeId, employeeId);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java
index 8a592ba..03e93cf 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeEmergencyContactMapper.java
@@ -21,4 +21,8 @@
                 .orderByAsc(HrmEmployeeEmergencyContactDO::getSort));
     }
 
+    default void deleteByEmployeeId(Long employeeId) {
+        delete(HrmEmployeeEmergencyContactDO::getEmployeeId, employeeId);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java
index 465e9c9..cb1b988 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeWorkHistoryMapper.java
@@ -19,4 +19,8 @@
         return selectList(HrmEmployeeWorkHistoryDO::getEmployeeId, employeeId);
     }
 
+    default void deleteByEmployeeId(Long employeeId) {
+        delete(HrmEmployeeWorkHistoryDO::getEmployeeId, employeeId);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index 72fadf6..58632cf 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -113,24 +113,24 @@
         employeeMapper.insert(employee);
         Long employeeId = employee.getId();
         // 7. 鍚屾娣诲姞鏁欒偛缁忓巻
-        if (CollUtil.isNotEmpty(createReqVO.getEducationList())) {
-            createReqVO.getEducationList().forEach(educationVO -> {
+        if (CollUtil.isNotEmpty(createReqVO.getEducations())) {
+            createReqVO.getEducations().forEach(educationVO -> {
                 HrmEmployeeEducationDO education = BeanUtils.toBean(educationVO, HrmEmployeeEducationDO.class);
                 education.setEmployeeId(employeeId);
                 educationMapper.insert(education);
             });
         }
         // 8. 鍚屾娣诲姞宸ヤ綔缁忓巻
-        if (CollUtil.isNotEmpty(createReqVO.getWorkHistoryList())) {
-            createReqVO.getWorkHistoryList().forEach(workHistoryVO -> {
+        if (CollUtil.isNotEmpty(createReqVO.getWorkHistories())) {
+            createReqVO.getWorkHistories().forEach(workHistoryVO -> {
                 HrmEmployeeWorkHistoryDO workHistory = BeanUtils.toBean(workHistoryVO, HrmEmployeeWorkHistoryDO.class);
                 workHistory.setEmployeeId(employeeId);
                 workHistoryMapper.insert(workHistory);
             });
         }
         // 9. 鍚屾娣诲姞绱ф�ヨ仈绯讳汉
-        if (CollUtil.isNotEmpty(createReqVO.getEmergencyContactList())) {
-            createReqVO.getEmergencyContactList().forEach(emergencyContactVO -> {
+        if (CollUtil.isNotEmpty(createReqVO.getEmergencyContacts())) {
+            createReqVO.getEmergencyContacts().forEach(emergencyContactVO -> {
                 HrmEmployeeEmergencyContactDO emergencyContact = BeanUtils.toBean(emergencyContactVO, HrmEmployeeEmergencyContactDO.class);
                 emergencyContact.setEmployeeId(employeeId);
                 emergencyContactMapper.insert(emergencyContact);
@@ -148,6 +148,7 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void updateEmployee(HrmEmployeeSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
         HrmEmployeeDO employee = validateEmployeeExists(updateReqVO.getId());
@@ -160,20 +161,69 @@
                 && employeeMapper.selectByPhone(updateReqVO.getPhone()) != null) {
             throw exception(EMPLOYEE_PHONE_EXISTS);
         }
-        // 鏇存柊
+        // 鏇存柊鍛樺伐涓昏〃
         HrmEmployeeDO updateObj = BeanUtils.toBean(updateReqVO, HrmEmployeeDO.class);
         // roleIds 瀛樺偍涓� JSON
         if (CollUtil.isNotEmpty(updateReqVO.getRoleIds())) {
             updateObj.setRoleIds(JSONUtil.toJsonStr(updateReqVO.getRoleIds()));
         }
         employeeMapper.updateById(updateObj);
+        Long employeeId = updateReqVO.getId();
+
+        // 鍚屾鏁欒偛缁忓巻
+        syncEducationList(employeeId, updateReqVO.getEducations());
+        // 鍚屾宸ヤ綔缁忓巻
+        syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories());
+        // 鍚屾绱ф�ヨ仈绯讳汉
+        syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts());
+    }
+
+    private void syncEducationList(Long employeeId, List<HrmEmployeeEducationSaveReqVO> newList) {
+        educationMapper.deleteByEmployeeId(employeeId);
+        if (CollUtil.isNotEmpty(newList)) {
+            newList.forEach(vo -> {
+                HrmEmployeeEducationDO education = BeanUtils.toBean(vo, HrmEmployeeEducationDO.class);
+                education.setId(null);
+                education.setEmployeeId(employeeId);
+                educationMapper.insert(education);
+            });
+        }
+    }
+
+    private void syncWorkHistoryList(Long employeeId, List<HrmEmployeeWorkHistorySaveReqVO> newList) {
+        workHistoryMapper.deleteByEmployeeId(employeeId);
+        if (CollUtil.isNotEmpty(newList)) {
+            newList.forEach(vo -> {
+                HrmEmployeeWorkHistoryDO workHistory = BeanUtils.toBean(vo, HrmEmployeeWorkHistoryDO.class);
+                workHistory.setId(null);
+                workHistory.setEmployeeId(employeeId);
+                workHistoryMapper.insert(workHistory);
+            });
+        }
+    }
+
+    private void syncEmergencyContactList(Long employeeId, List<HrmEmployeeEmergencyContactSaveReqVO> newList) {
+        emergencyContactMapper.deleteByEmployeeId(employeeId);
+        if (CollUtil.isNotEmpty(newList)) {
+            newList.forEach(vo -> {
+                HrmEmployeeEmergencyContactDO emergencyContact = BeanUtils.toBean(vo, HrmEmployeeEmergencyContactDO.class);
+                emergencyContact.setId(null);
+                emergencyContact.setEmployeeId(employeeId);
+                emergencyContactMapper.insert(emergencyContact);
+            });
+        }
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteEmployee(Long id) {
         // 鏍¢獙瀛樺湪
         validateEmployeeExists(id);
-        // 鍒犻櫎
+        // 绾ц仈鍒犻櫎瀛愯〃
+        educationMapper.deleteByEmployeeId(id);
+        workHistoryMapper.deleteByEmployeeId(id);
+        emergencyContactMapper.deleteByEmployeeId(id);
+        // 鍒犻櫎鍛樺伐
         employeeMapper.deleteById(id);
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java
index d680868..3d57e21 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanRespVO.java
@@ -52,6 +52,10 @@
     @ExcelProperty("鐘舵��")
     private Integer status;
 
+    @Schema(description = "鏄惁鎺掗櫎鍛ㄦ湯", example = "true")
+    @ExcelProperty("鏄惁鎺掗櫎鍛ㄦ湯")
+    private Boolean excludeWeekend;
+
     @Schema(description = "澶囨敞")
     @ExcelProperty("澶囨敞")
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java
index 4de1501..a7d9af5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/cal/plan/vo/MesCalPlanSaveReqVO.java
@@ -47,7 +47,10 @@
     @Schema(description = "鐘舵��", example = "0")
     private Integer status;
 
+    @Schema(description = "鏄惁鎺掗櫎鍛ㄦ湯", example = "true")
+    private Boolean excludeWeekend;
+
     @Schema(description = "澶囨敞")
     private String remark;
 
-    }
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
index c2d586c..a44b286 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/MesProWorkOrderBomController.java
@@ -12,14 +12,24 @@
 import cn.iocoder.yudao.module.mes.controller.admin.pro.workorder.vo.bom.MesProWorkOrderBomSaveReqVO;
 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.item.MesMdProductBomDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.unitmeasure.MesMdUnitMeasureDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderBomDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.pro.workorder.MesProWorkOrderProcessDO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.wm.materialstock.MesWmMaterialStockDO;
+import cn.iocoder.yudao.module.mes.dal.mysql.wm.materialstock.MesWmMaterialStockMapper;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
-import cn.iocoder.yudao.module.mes.service.md.item.MesMdProductBomService;
 import cn.iocoder.yudao.module.mes.service.md.unitmeasure.MesMdUnitMeasureService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductBomService;
+import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
 import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderBomService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderProcessService;
+import cn.iocoder.yudao.module.mes.service.pro.workorder.MesProWorkOrderService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -31,6 +41,7 @@
 
 import java.math.BigDecimal;
 import java.util.*;
+import java.util.Comparator;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
@@ -44,13 +55,23 @@
     @Resource
     private MesProWorkOrderBomService workOrderBomService;
     @Resource
+    private MesProWorkOrderService workOrderService;
+    @Resource
+    private MesProWorkOrderProcessService workOrderProcessService;
+    @Resource
     private MesMdItemService itemService;
     @Resource
     private MesMdUnitMeasureService unitMeasureService;
     @Resource
     private MesMdItemTypeService itemTypeService;
     @Resource
-    private MesMdProductBomService productBomService;
+    private MesProRouteProductService routeProductService;
+    @Resource
+    private MesProRouteProductBomService routeProductBomService;
+    @Resource
+    private MesWmMaterialStockMapper materialStockMapper;
+    @Resource
+    private ErpPurchaseOrderApi purchaseOrderApi;
 
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓宸ュ崟 BOM")
@@ -102,40 +123,67 @@
     @PreAuthorize("@ss.hasPermission('mes:pro-work-order:query')")
     public CommonResult<List<MesProWorkOrderItemRespVO>> getWorkOrderBomItemListByWorkOrderId(
             @RequestParam("workOrderId") Long workOrderId) {
-        // 1. 鑾峰彇宸ュ崟 BOM 琛�
+        // 1. 鑾峰彇宸ュ崟
+        MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(workOrderId);
+        if (workOrder == null) {
+            return success(Collections.emptyList());
+        }
+
+        // 2. 鑾峰彇宸ュ崟宸ュ簭鍒楄〃锛堝惈鍚勫伐搴忕殑鎶曟枡鏄庣粏涓庝骇鍑哄搧锛�
+        List<MesProWorkOrderProcessDO> processList = workOrderProcessService
+                .getWorkOrderProcessListByWorkOrderId(workOrderId);
+        if (CollUtil.isEmpty(processList)) {
+
+            return success(Collections.emptyList());
+        }
+
+        // 3. 鑾峰彇宸ュ崟 BOM 琛岋紙鍚勫伐搴忕殑鎶曟枡璁板綍锛屾寜 processId 鍒嗙粍浣跨敤锛�
         List<MesProWorkOrderBomDO> bomList = workOrderBomService.getWorkOrderBomListByWorkOrderId(workOrderId);
         if (CollUtil.isEmpty(bomList)) {
             return success(Collections.emptyList());
         }
 
-        // 2. 閫愬眰灞曞紑锛屽緱鍒板彾瀛愮墿鏂� itemId 鈫� quantity
-        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(bomList);
+        // 4. 閫氳繃浜у搧ID鍙嶆煡宸ヨ壓璺嚎锛堝伐鍗� routeId 鍙兘涓虹┖锛�
+        Long routeId = null;
+        if (workOrder.getProductId() != null) {
+            MesProRouteProductDO routeProduct = routeProductService.getRouteProductByItemId(workOrder.getProductId());
+            if (routeProduct != null) {
+                routeId = routeProduct.getRouteId();
+            }
+        }
+
+        // 5. 鎸夊伐搴忎骇鍑洪摼閫愬眰灞曞紑 BOM锛屽緱鍒板彾瀛愮墿鏂欙紙姣忓崟浣嶆垚鍝佺殑鐢ㄩ噺锛�
+        Map<Long, BigDecimal> leafItems = buildWorkOrderItems(processList, bomList, routeId);
         if (CollUtil.isEmpty(leafItems)) {
             return success(Collections.emptyList());
         }
 
-        // 3. 鎷兼帴 VO 瀛楁
-        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(leafItems.keySet());
-        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
-                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
-        Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
-                convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
-        List<MesProWorkOrderItemRespVO> result = new ArrayList<>(leafItems.size());
+        // 6. 涔樹互宸ュ崟鐢熶骇鏁伴噺锛屽緱鍒版�婚渶姹�
+        BigDecimal orderQty = workOrder.getQuantity() != null ? workOrder.getQuantity() : BigDecimal.ONE;
+        Map<Long, BigDecimal> totalRequirements = new LinkedHashMap<>();
         for (Map.Entry<Long, BigDecimal> entry : leafItems.entrySet()) {
-            MesMdItemDO item = itemMap.get(entry.getKey());
-            if (item == null) {
-                continue;
-            }
-            MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
-                    .setItemId(item.getId()).setQuantity(entry.getValue())
-                    .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification());
-            MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
-                    unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
-            MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(),
-                    itemType -> vo.setItemOrProduct(itemType.getItemOrProduct()));
-            result.add(vo);
+            totalRequirements.put(entry.getKey(), entry.getValue().multiply(orderQty));
         }
-        return success(result);
+
+        // 7. 鏌ヨ鏉ユ簮涓烘湰宸ュ崟鐨勯噰璐鍗曟槑缁嗭紝鎸変骇鍝佹眹鎬诲湪閫旈噰璐暟
+        //    sourceType=1 琛ㄧず鏉ユ簮绫诲瀷涓�"鐢熶骇宸ュ崟"
+        Map<Long, BigDecimal> purchaseOrderQtyMap = new HashMap<>();
+        List<ErpPurchaseOrderItemRespDTO> poItems = purchaseOrderApi
+                .getPurchaseOrderItemListBySource(1, workOrderId)
+                .getCheckedData();
+        if (CollUtil.isNotEmpty(poItems)) {
+            for (ErpPurchaseOrderItemRespDTO poItem : poItems) {
+                // 鍦ㄩ�� = 璁㈣喘鏁伴噺 - 宸插叆搴撴暟閲�
+                BigDecimal inCount = poItem.getInCount() != null ? poItem.getInCount() : BigDecimal.ZERO;
+                BigDecimal inTransit = poItem.getCount().subtract(inCount);
+                if (inTransit.compareTo(BigDecimal.ZERO) > 0) {
+                    purchaseOrderQtyMap.merge(poItem.getProductId(), inTransit, BigDecimal::add);
+                }
+            }
+        }
+
+        // 8. 鏌ヨ搴撳瓨锛屾嫾鎺� VO
+        return success(buildItemRespVOList(totalRequirements, purchaseOrderQtyMap));
     }
 
     // ==================== 鎷兼帴 VO ====================
@@ -164,47 +212,160 @@
         });
     }
 
-    // ==================== 鐗╂枡闇�姹傞�愬眰灞曞紑 ====================
+    // ==================== 鐗╂枡闇�姹傚睍寮�锛堝伐搴忎骇鍑洪摼 + 璺敱浜у搧 BOM锛� ====================
 
     /**
-     * 閫愬眰 IN 鏌ヨ灞曞紑宸ュ崟 BOM 鍒板彾瀛愮墿鏂欙紝鍚堝苟鍚岀墿鏂欑殑闇�姹傛暟閲�
+     * 鎸夊伐搴忎骇鍑洪摼閫愬眰灞曞紑鐗╂枡闇�姹傘��
      *
-     * @param bomList 宸ュ崟 BOM 鍒楄〃
-     * @return 鍙跺瓙鐗╂枡 Map锛歩temId 鈫� 鍚堝苟鍚庣殑闇�姹傛暟閲�
+     * <p><b>鏍稿績閫昏緫锛�</b></p>
+     * <ol>
+     *   <li>鎵惧嚭鍏抽敭宸ュ簭锛坘eyFlag=true锛夛紝浠ュ畠鐨勬姇鏂欎负灞曞紑璧风偣</li>
+     *   <li>鏋勫缓浜у嚭鍝佲啋宸ュ簭鎶曟枡鐨勬槧灏勶紙{@code outputItemId 鈫� per-unit bomItems}锛�</li>
+     *   <li>閫愬眰灞曞紑锛氳嫢鐗╂枡鏄煇宸ュ簭鐨勪骇鍑哄搧 鈫� 鐢ㄨ宸ュ簭鐨勬姇鏂欓厤鏂规浛浠o紱
+     *       鍚﹀垯鏌ヨ矾鐢变骇鍝� BOM 鈫� 鐢� BOM 瀛愰」鏇夸唬锛�
+     *       涓よ�呴兘娌℃湁 鈫� 鍙跺瓙鐗╂枡锛堝璐欢锛夛紝绱姞鍒扮粨鏋�</li>
+     * </ol>
+     *
+     * <p>杩欐牱閬垮厤浜嗘妸涓棿宸ュ簭鐨勬姇鏂欑洿鎺ョ撼鍏ヨ捣鐐瑰鑷寸殑閲嶅璁$畻闂銆�</p>
+     *
+     * @param processList 宸ュ崟宸ュ簭鍒楄〃锛堟寜 sort 鍗囧簭锛�
+     * @param bomList     宸ュ崟 BOM 琛岋紙鎵�鏈夊伐搴忕殑鎶曟枡鏄庣粏锛�
+     * @param routeId     宸ヨ壓璺嚎缂栧彿锛堝彲涓� null锛宯ull 鏃惰烦杩囪矾鐢� BOM 鏌ヨ锛�
+     * @return 鍙跺瓙鐗╂枡 Map锛歩temId 鈫� 闇�姹傛暟閲�
      */
-    private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderBomDO> bomList) {
-        // 1. 鍒濆鍖栵細鏀堕泦绗竴灞傜墿鏂� ID 鍜屽搴旀暟閲�
-        Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
-        for (MesProWorkOrderBomDO bom : bomList) {
-            currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
+    private Map<Long, BigDecimal> buildWorkOrderItems(List<MesProWorkOrderProcessDO> processList,
+                                                       List<MesProWorkOrderBomDO> bomList,
+                                                       Long routeId) {
+        // 1. 鏋勫缓浜у嚭鍝� 鈫� 宸ュ簭鎶曟枡閰嶆柟锛坧er-unit锛�
+        //    outputItemId 鈫� [{itemId, quantity}, ...]
+        Map<Long, List<MesProWorkOrderBomDO>> processBomMap = new HashMap<>();
+        for (MesProWorkOrderProcessDO process : processList) {
+            if (process.getOutputItemId() == null) {
+                continue;
+            }
+            List<MesProWorkOrderBomDO> items = new ArrayList<>();
+            for (MesProWorkOrderBomDO bom : bomList) {
+                if (bom.getProcessId().equals(process.getProcessId())) {
+                    items.add(bom);
+                }
+            }
+            processBomMap.put(process.getOutputItemId(), items);
         }
 
-        // 2. 閫愬眰 IN 鏌ヨ灞曞紑 BOM
+        // 2. 鍙栨渶鍚庝竴閬撳伐搴忥紙sort 鏈�澶э級浣滀负灞曞紑璧风偣锛岄�愬眰鍚戝墠閫掓帹
+        MesProWorkOrderProcessDO lastProcess = processList.stream()
+                .max(Comparator.comparing(MesProWorkOrderProcessDO::getSort))
+                .orElse(null);
+        if (lastProcess == null) {
+            return Collections.emptyMap();
+        }
+
+        // 3. 浠ユ渶鍚庝竴閬撳伐搴忕殑鎶曟枡涓鸿捣鐐�
+        Map<Long, BigDecimal> currentLayer = new LinkedHashMap<>();
+        for (MesProWorkOrderBomDO bom : bomList) {
+            if (bom.getProcessId().equals(lastProcess.getProcessId())) {
+                currentLayer.merge(bom.getItemId(), bom.getQuantity(), BigDecimal::add);
+            }
+        }
+        if (CollUtil.isEmpty(currentLayer)) {
+            return Collections.emptyMap();
+        }
+
+        // 4. 閫愬眰灞曞紑
         Map<Long, BigDecimal> leafItems = new LinkedHashMap<>();
         for (int i = 0; i < Byte.MAX_VALUE && CollUtil.isNotEmpty(currentLayer); i++) {
-            // 2.1 鎵归噺鏌ヨ褰撳墠灞傛墍鏈夌墿鏂欑殑瀛� BOM
-            List<MesMdProductBomDO> subBomList = productBomService.getProductBomListByItemIds(currentLayer.keySet());
-            // 2.2 鎸� itemId 鍒嗙粍
-            Map<Long, List<MesMdProductBomDO>> subBomMap = convertMultiMap(subBomList, MesMdProductBomDO::getItemId);
-            // 2.3 鍖哄垎鍙跺瓙鑺傜偣鍜岄潪鍙跺瓙鑺傜偣
             Map<Long, BigDecimal> nextLayer = new LinkedHashMap<>();
+
+            // 4.1 鎵归噺鏌ヨ璺敱浜у搧 BOM锛堢敤浜庨潪宸ュ簭浜у嚭鐨勭墿鏂欏睍寮�锛�
+            Map<Long, List<MesProRouteProductBomDO>> routeSubBomMap;
+            if (routeId != null) {
+                List<MesProRouteProductBomDO> routeSubBomList = routeProductBomService
+                        .getRouteProductBomListByProductIds(routeId, currentLayer.keySet());
+                routeSubBomMap = convertMultiMap(routeSubBomList, MesProRouteProductBomDO::getProductId);
+            } else {
+                routeSubBomMap = Collections.emptyMap();
+            }
+
             for (Map.Entry<Long, BigDecimal> entry : currentLayer.entrySet()) {
                 Long itemId = entry.getKey();
-                BigDecimal quantity = entry.getValue();
-                List<MesMdProductBomDO> children = subBomMap.get(itemId);
-                // 鎯呭喌涓�锛氬彾瀛愯妭鐐癸紝绱姞鍒扮粨鏋�
-                if (CollUtil.isEmpty(children)) {
-                    leafItems.merge(itemId, quantity, BigDecimal::add);
+                BigDecimal qty = entry.getValue();
+
+                // 鎯呭喌涓�锛氱墿鏂欐槸鏌愬伐搴忕殑浜у嚭鍝� 鈫� 鐢ㄥ伐搴忛厤鏂规浛浠�
+                List<MesProWorkOrderBomDO> processBom = processBomMap.get(itemId);
+                if (CollUtil.isNotEmpty(processBom)) {
+                    for (MesProWorkOrderBomDO child : processBom) {
+                        nextLayer.merge(child.getItemId(), qty.multiply(child.getQuantity()), BigDecimal::add);
+                    }
                     continue;
                 }
-                // 鎯呭喌浜岋細闈炲彾瀛愶紝瀛愮墿鏂欒繘鍏ヤ笅涓�灞�
-                for (MesMdProductBomDO child : children) {
-                    nextLayer.merge(child.getBomItemId(), quantity.multiply(child.getQuantity()), BigDecimal::add);
+
+                // 鎯呭喌浜岋細璺敱浜у搧 BOM 涓湁涓嬬骇 鈫� 灞曞紑
+                List<MesProRouteProductBomDO> routeChildren = routeSubBomMap.get(itemId);
+                if (CollUtil.isNotEmpty(routeChildren)) {
+                    for (MesProRouteProductBomDO child : routeChildren) {
+                        nextLayer.merge(child.getItemId(), qty.multiply(child.getQuantity()), BigDecimal::add);
+                    }
+                    continue;
                 }
+
+                // 鎯呭喌涓夛細鍙跺瓙鐗╂枡
+                leafItems.merge(itemId, qty, BigDecimal::add);
             }
             currentLayer = nextLayer;
         }
         return leafItems;
     }
 
+    // ==================== 鎷兼帴鐗╂枡闇�姹� VO锛堝惈搴撳瓨鏍¢獙锛� ====================
+
+    private List<MesProWorkOrderItemRespVO> buildItemRespVOList(Map<Long, BigDecimal> requirements,
+                                                                  Map<Long, BigDecimal> purchaseOrderQtyMap) {
+        // 1. 鎵归噺鑾峰彇鐗╂枡鍩虹淇℃伅
+        Map<Long, MesMdItemDO> itemMap = itemService.getItemMap(requirements.keySet());
+        Map<Long, MesMdUnitMeasureDO> unitMeasureMap = unitMeasureService.getUnitMeasureMap(
+                convertSet(itemMap.values(), MesMdItemDO::getUnitMeasureId));
+        Map<Long, MesMdItemTypeDO> itemTypeMap = itemTypeService.getItemTypeMap(
+                convertSet(itemMap.values(), MesMdItemDO::getItemTypeId));
+
+        // 2. 鎵归噺鏌ヨ搴撳瓨锛屾寜 itemId 姹囨�诲彲鐢ㄩ噺锛堝湪搴� - 鍐荤粨 - 鍗犵敤锛�
+        Map<Long, BigDecimal> stockMap = new HashMap<>();
+        List<MesWmMaterialStockDO> stockList = materialStockMapper.selectListByItemIds(requirements.keySet());
+        for (MesWmMaterialStockDO stock : stockList) {
+            BigDecimal frozen = stock.getFrozenQuantity() != null ? stock.getFrozenQuantity() : BigDecimal.ZERO;
+            BigDecimal reserved = stock.getReservedQuantity() != null ? stock.getReservedQuantity() : BigDecimal.ZERO;
+            BigDecimal available = stock.getQuantity().subtract(frozen).subtract(reserved);
+            if (available.compareTo(BigDecimal.ZERO) > 0) {
+                stockMap.merge(stock.getItemId(), available, BigDecimal::add);
+            }
+        }
+
+        // 3. 鎷兼帴 VO
+        List<MesProWorkOrderItemRespVO> result = new ArrayList<>(requirements.size());
+        for (Map.Entry<Long, BigDecimal> entry : requirements.entrySet()) {
+            MesMdItemDO item = itemMap.get(entry.getKey());
+            if (item == null) {
+                continue;
+            }
+            BigDecimal requiredQty = entry.getValue();
+            BigDecimal stockQty = stockMap.getOrDefault(item.getId(), BigDecimal.ZERO);
+            BigDecimal purchaseQty = purchaseOrderQtyMap.getOrDefault(item.getId(), BigDecimal.ZERO);
+
+            // 鍏呰冻鍒ゆ柇 = 鍙敤搴撳瓨 + 鍦ㄩ�旈噰璐暟 >= 闇�姹傛暟閲�
+            BigDecimal totalSupply = stockQty.add(purchaseQty);
+
+            MesProWorkOrderItemRespVO vo = new MesProWorkOrderItemRespVO()
+                    .setItemId(item.getId()).setQuantity(requiredQty)
+                    .setItemCode(item.getCode()).setItemName(item.getName()).setItemSpecification(item.getSpecification())
+                    .setStockQuantity(stockQty)
+                    .setPurchaseOrderQuantity(purchaseQty)
+                    .setStockSufficient(totalSupply.compareTo(requiredQty) >= 0);
+            MapUtils.findAndThen(unitMeasureMap, item.getUnitMeasureId(),
+                    unitMeasure -> vo.setUnitMeasureName(unitMeasure.getName()));
+            MapUtils.findAndThen(itemTypeMap, item.getItemTypeId(),
+                    itemType -> vo.setItemOrProduct(itemType.getItemOrProduct()));
+            result.add(vo);
+        }
+        return result;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java
index 922bc24..c064071 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pro/workorder/vo/MesProWorkOrderItemRespVO.java
@@ -27,6 +27,15 @@
     @Schema(description = "闇�姹傛暟閲�", example = "1000.00")
     private BigDecimal quantity;
 
+    @Schema(description = "鍙敤搴撳瓨", example = "500.00")
+    private BigDecimal stockQuantity;
+
+    @Schema(description = "鍦ㄩ�旈噰璐暟锛堟潵婧愪负鏈伐鍗曠殑閲囪喘璁㈠崟鏁伴噺姹囨�伙級", example = "200.00")
+    private BigDecimal purchaseOrderQuantity;
+
+    @Schema(description = "搴撳瓨鏄惁鍏呰冻锛堝彲鐢ㄥ簱瀛� + 鍦ㄩ�旈噰璐暟 >= 闇�姹傛暟閲忥級", example = "true")
+    private Boolean stockSufficient;
+
     @Schema(description = "鐗╂枡/浜у搧鏍囪瘑", example = "ITEM")
     private String itemOrProduct;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java
index d6ba341..35af7ca 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/salesnotice/vo/line/MesWmSalesNoticeLineSaveReqVO.java
@@ -18,6 +18,9 @@
     @NotNull(message = "鍙戣揣閫氱煡鍗曠紪鍙蜂笉鑳戒负绌�")
     private Long noticeId;
 
+    @Schema(description = "閿�鍞鍗曟槑缁咺D", example = "1")
+    private Long saleOrderItemId;
+
     @Schema(description = "鐗╂枡缂栧彿", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
     @NotNull(message = "鐗╂枡缂栧彿涓嶈兘涓虹┖")
     private Long itemId;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/plan/MesCalPlanDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/plan/MesCalPlanDO.java
index 15eb13b..72827a0 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/plan/MesCalPlanDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/cal/plan/MesCalPlanDO.java
@@ -78,6 +78,14 @@
      */
     private Integer status;
     /**
+     * 鏄惁鎺掗櫎鍛ㄦ湯
+     *
+     * true锛氬懆鍏棩涓嶇敓鎴愭帓鐝褰曪紙榛樿锛�
+     * false锛氬懆鍏棩姝e父鎺掔彮锛堝娉ㄥ杞﹂棿闇�瑕佽繛缁敓浜э級
+     */
+    private Boolean excludeWeekend;
+
+    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/route/MesProRouteProductBomMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/route/MesProRouteProductBomMapper.java
index 832c549..f14dced 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/route/MesProRouteProductBomMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/pro/route/MesProRouteProductBomMapper.java
@@ -5,6 +5,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
 import org.apache.ibatis.annotations.Mapper;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -46,4 +47,10 @@
                 .eq(MesProRouteProductBomDO::getProductId, productId));
     }
 
+    default List<MesProRouteProductBomDO> selectListByProductIds(Long routeId, Collection<Long> productIds) {
+        return selectList(new LambdaQueryWrapperX<MesProRouteProductBomDO>()
+                .eq(MesProRouteProductBomDO::getRouteId, routeId)
+                .in(MesProRouteProductBomDO::getProductId, productIds));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
index fd04fdd..a8b26d5 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/batch/MesWmBatchMapper.java
@@ -72,12 +72,12 @@
                 .betweenIfPresent(MesWmBatchDO::getExpireDate, reqVO.getExpireDate())
                 .betweenIfPresent(MesWmBatchDO::getReceiptDate, reqVO.getReceiptDate())
                 .orderByDesc(MesWmBatchDO::getId);
-        if (permittedClientIds != null) {
-            if (CollUtil.isEmpty(permittedClientIds)) {
-                return PageResult.empty();
-            }
-            wrapper.in(MesWmBatchDO::getClientId, permittedClientIds);
-        }
+//        if (permittedClientIds != null) {
+//            if (CollUtil.isEmpty(permittedClientIds)) {
+//                return PageResult.empty();
+//            }
+//            wrapper.in(MesWmBatchDO::getClientId, permittedClientIds);
+//        }
         return selectPage(reqVO, wrapper);
     }
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
index 5769273..38a7df2 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/wm/materialstock/MesWmMaterialStockMapper.java
@@ -137,12 +137,13 @@
         } else {
             wrapper.isNull(MesWmMaterialStockDO::getBatchId);
         }
-        // vendorId=null 鏃剁簿纭尮閰� is null
+        // vendorId=null 鏃朵笉闄愬埗锛屽尮閰嶄换鎰忎緵搴斿晢鐨勫簱瀛�
+        // 锛堥伩鍏嶅洜渚涘簲鍟嗙淮搴︽媶鍒嗭紝OUT 浜嬪姟鎵句笉鍒扮湡姝f湁搴撳瓨鐨勮褰曡�屾墸閿欏埌 vendor_id IS NULL 鐨勮褰曪級
         if (vendorId != null) {
             wrapper.eq(MesWmMaterialStockDO::getVendorId, vendorId);
-        } else {
-            wrapper.isNull(MesWmMaterialStockDO::getVendorId);
         }
+        // 澶氳褰曟椂浼樺厛鍙栧簱瀛橀噺鏈�澶氱殑锛堥伩鍏嶆墸鍒伴浂搴撳瓨鎴栬礋搴撳瓨鐨勮褰曪級
+        wrapper.last("ORDER BY quantity DESC LIMIT 1");
         return selectOne(wrapper);
     }
 
@@ -183,4 +184,16 @@
                 .orderByAsc(MesWmMaterialStockDO::getReceiptTime));
     }
 
+    /**
+     * 鎸夌墿鏂欑紪鍙锋壒閲忔煡璇㈠簱瀛樿褰曪紙鐢ㄤ簬鐗╂枡闇�姹傚簱瀛樻牎楠岋級
+     */
+    default List<MesWmMaterialStockDO> selectListByItemIds(Collection<Long> itemIds) {
+        if (itemIds == null || itemIds.isEmpty()) {
+            return java.util.Collections.emptyList();
+        }
+        return selectList(new LambdaQueryWrapperX<MesWmMaterialStockDO>()
+                .in(MesWmMaterialStockDO::getItemId, itemIds)
+                .ne(MesWmMaterialStockDO::getQuantity, BigDecimal.ZERO));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
index 8dbb2dc..9192b5b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/cal/team/MesCalTeamShiftServiceImpl.java
@@ -1,13 +1,16 @@
 package cn.iocoder.yudao.module.mes.service.cal.team;
 
 import cn.iocoder.yudao.module.mes.controller.admin.cal.team.vo.shift.MesCalTeamShiftListReqVO;
+import cn.iocoder.yudao.module.mes.dal.dataobject.cal.holiday.MesCalHolidayDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanShiftDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.plan.MesCalPlanTeamDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.cal.team.MesCalTeamShiftDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.cal.team.MesCalTeamShiftMapper;
+import cn.iocoder.yudao.module.mes.enums.cal.MesCalHolidayTypeEnum;
 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftMethodEnum;
 import cn.iocoder.yudao.module.mes.enums.cal.MesCalShiftTypeEnum;
+import cn.iocoder.yudao.module.mes.service.cal.holiday.MesCalHolidayService;
 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanService;
 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanShiftService;
 import cn.iocoder.yudao.module.mes.service.cal.plan.MesCalPlanTeamService;
@@ -18,11 +21,14 @@
 import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.time.DayOfWeek;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
 import java.time.temporal.ChronoUnit;
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.mes.enums.ErrorCodeConstants.CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH;
@@ -49,6 +55,9 @@
     @Resource
     @Lazy
     private MesCalPlanTeamService planTeamService;
+    @Resource
+    @Lazy
+    private MesCalHolidayService holidayService;
 
     @Transactional(rollbackFor = Exception.class)
     @Override
@@ -69,23 +78,56 @@
             throw exception(CAL_TEAM_SHIFT_GENERATE_SHIFT_NOT_ENOUGH);
         }
 
-        // 2. 璁$畻鏃ユ湡璺ㄥ害锛岄亶鍘嗘瘡澶╃敓鎴愭帓鐝褰曪紝鏈�鍚庣粺涓�鍐欏叆鏁版嵁搴�
+        // 1.5 鍔犺浇鏃ユ湡鍖洪棿鍐呯殑鍋囨棩鏁版嵁锛屾瀯寤� Map<鏃ユ湡, 绫诲瀷>
+        Map<LocalDate, Integer> holidayMap = holidayService.getHolidayList(plan.getStartDate(), plan.getEndDate())
+                .stream().collect(Collectors.toMap(h -> h.getDay().toLocalDate(), MesCalHolidayDO::getType));
+
+        // 2. 璁$畻鏃ユ湡璺ㄥ害锛岄�愬ぉ鍒ゆ柇宸ヤ綔鏃ワ紝鐢熸垚鎺掔彮璁板綍
         LocalDate startDate = plan.getStartDate().toLocalDate();
         LocalDate endDate = plan.getEndDate().toLocalDate();
         long days = ChronoUnit.DAYS.between(startDate, endDate) + 1;
+        boolean excludeWeekend = plan.getExcludeWeekend() != null && plan.getExcludeWeekend();
         List<MesCalTeamShiftDO> allRecords = new ArrayList<>();
         int shiftIndex = 0;
+        int workdayOffset = 0; // 浠呭伐浣滄棩閫掑锛岀敤浜庡�掔彮杞崲璁℃暟
         for (int i = 0; i < days; i++) {
             LocalDate currentDate = startDate.plusDays(i);
+            // 2.1 璺宠繃闈炲伐浣滄棩
+            if (!isWorkday(currentDate, holidayMap, excludeWeekend)) {
+                continue;
+            }
             LocalDateTime currentDay = currentDate.atStartOfDay();
-            // 2.2.1 鏍规嵁鍊掔彮鏂瑰紡璁$畻 shiftIndex
-            shiftIndex = calculateShiftIndex(shiftIndex, i, currentDate, startDate, plan);
+            // 2.2.1 鏍规嵁鍊掔彮鏂瑰紡璁$畻 shiftIndex锛堜互宸ヤ綔鏃ュ亸绉讳负鍑嗭級
+            shiftIndex = calculateShiftIndex(shiftIndex, workdayOffset, currentDate, startDate, plan);
             // 2.2.2 鏍规嵁杞彮鏂瑰紡鐢熸垚鎺掔彮璁板綍锛屾敹闆嗗埌鍒楄〃
             buildRecordsForDay(allRecords, planId, currentDay, shiftIndex, plan.getShiftType(), teams, shifts);
+            workdayOffset++;
         }
 
         // 3. 鎵归噺鎻掑叆鎺掔彮璁板綍
         teamShiftMapper.insertBatch(allRecords);
+    }
+
+    /**
+     * 鍒ゆ柇鎸囧畾鏃ユ湡鏄惁涓哄伐浣滄棩
+     *
+     * 浼樺厛绾э細holiday 琛� > 鍛ㄦ湯鎺掗櫎寮�鍏� > 榛樿宸ヤ綔鏃�
+     */
+    private boolean isWorkday(LocalDate date, Map<LocalDate, Integer> holidayMap, boolean excludeWeekend) {
+        // 1. holiday 琛ㄤ紭鍏堬細鏄惧紡鏍囪鐨勬棩鏈熶互鏍囪涓哄噯
+        Integer holidayType = holidayMap.get(date);
+        if (holidayType != null) {
+            return MesCalHolidayTypeEnum.WORKDAY.getType().equals(holidayType);
+        }
+        // 2. 鑻ュ紑鍚簡鎺掗櫎鍛ㄦ湯锛屽懆鍏棩榛樿涓轰紤鎭棩
+        if (excludeWeekend) {
+            DayOfWeek dow = date.getDayOfWeek();
+            if (dow == DayOfWeek.SATURDAY || dow == DayOfWeek.SUNDAY) {
+                return false;
+            }
+        }
+        // 3. 榛樿宸ヤ綔鏃�
+        return true;
     }
 
     /**
@@ -96,6 +138,10 @@
         if (dayOffset == 0) {
             return shiftIndex; // 绗竴澶╀笉閫掑
         }
+        // 鍗曠櫧鐝笉闇�瑕佸�掔彮锛宻hiftMethod 涓� null锛岀洿鎺ヨ繑鍥�
+        if (plan.getShiftMethod() == null) {
+            return shiftIndex;
+        }
         if (MesCalShiftMethodEnum.QUARTER.getMethod().equals(plan.getShiftMethod())) {
             // 鎸夊搴﹁疆鐝細鍒颁簡鏂板搴︾殑绗竴澶�
             LocalDate quarterStart = LocalDateTimeUtils.getQuarterStart(currentDate);
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomService.java
index 09745db..9a50c14 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomService.java
@@ -4,6 +4,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pro.route.MesProRouteProductBomDO;
 import jakarta.validation.Valid;
 
+import java.util.Collection;
 import java.util.List;
 
 /**
@@ -62,4 +63,13 @@
      */
     List<MesProRouteProductBomDO> getRouteProductBomListByRouteIdAndProductId(Long routeId, Long productId);
 
+    /**
+     * 鎸夊伐鑹鸿矾绾垮拰澶氫釜浜у搧鎵归噺鏌ヨ BOM 鍒楄〃
+     *
+     * @param routeId 宸ヨ壓璺嚎缂栧彿
+     * @param productIds 浜у搧鐗╂枡缂栧彿闆嗗悎
+     * @return BOM 鍒楄〃
+     */
+    List<MesProRouteProductBomDO> getRouteProductBomListByProductIds(Long routeId, Collection<Long> productIds);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomServiceImpl.java
index d833683..ab8c209 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/route/MesProRouteProductBomServiceImpl.java
@@ -13,6 +13,7 @@
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.util.Collection;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -137,4 +138,12 @@
         return routeProductBomMapper.selectListByRouteIdAndProductId(routeId, productId);
     }
 
+    @Override
+    public List<MesProRouteProductBomDO> getRouteProductBomListByProductIds(Long routeId, Collection<Long> productIds) {
+        if (CollUtil.isEmpty(productIds)) {
+            return java.util.Collections.emptyList();
+        }
+        return routeProductBomMapper.selectListByProductIds(routeId, productIds);
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
index bed3c88..a84dda6 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/batch/MesWmBatchServiceImpl.java
@@ -97,122 +97,107 @@
         // 2. 鏍规嵁閰嶇疆鏍¢獙蹇呭~鍙傛暟骞舵竻绌轰笉闇�瑕佺殑鍙傛暟
         MesWmBatchDO batch = MesWmBatchDO.builder().itemId(reqVO.getItemId()).build();
         if (config != null) {
+            // ========== 蹇呭~鏍¢獙锛坒lag=true 琛ㄧず璇ュ瓧娈典负蹇呭~锛� ==========
             // 鐢熶骇鏃ユ湡
-            if (Boolean.TRUE.equals(config.getProduceDateFlag())) {
-                if (ObjUtil.isNull(reqVO.getProduceDate())) {
-                    throw exception(WM_BATCH_PRODUCE_DATE_REQUIRED);
-                }
-                batch.setProduceDate(reqVO.getProduceDate());
+            if (Boolean.TRUE.equals(config.getProduceDateFlag()) && ObjUtil.isNull(reqVO.getProduceDate())) {
+                throw exception(WM_BATCH_PRODUCE_DATE_REQUIRED);
             }
             // 鍏ュ簱鏃ユ湡
-            if (Boolean.TRUE.equals(config.getReceiptDateFlag())) {
-                if (ObjUtil.isNull(reqVO.getReceiptDate())) {
-                    throw exception(WM_BATCH_RECEIPT_DATE_REQUIRED);
-                }
-                batch.setReceiptDate(reqVO.getReceiptDate());
+            if (Boolean.TRUE.equals(config.getReceiptDateFlag()) && ObjUtil.isNull(reqVO.getReceiptDate())) {
+                throw exception(WM_BATCH_RECEIPT_DATE_REQUIRED);
             }
             // 鏈夋晥鏈�
-            if (Boolean.TRUE.equals(config.getExpireDateFlag())) {
-                if (ObjUtil.isNull(reqVO.getExpireDate())) {
-                    throw exception(WM_BATCH_EXPIRE_DATE_REQUIRED);
-                }
-                batch.setExpireDate(reqVO.getExpireDate());
+            if (Boolean.TRUE.equals(config.getExpireDateFlag()) && ObjUtil.isNull(reqVO.getExpireDate())) {
+                throw exception(WM_BATCH_EXPIRE_DATE_REQUIRED);
             }
             // 渚涘簲鍟�
-            if (Boolean.TRUE.equals(config.getVendorFlag())) {
-                if (ObjUtil.isNull(reqVO.getVendorId())) {
-                    throw exception(WM_BATCH_VENDOR_REQUIRED);
-                }
-                batch.setVendorId(reqVO.getVendorId());
+            if (Boolean.TRUE.equals(config.getVendorFlag()) && ObjUtil.isNull(reqVO.getVendorId())) {
+                throw exception(WM_BATCH_VENDOR_REQUIRED);
             }
             // 瀹㈡埛
-            if (Boolean.TRUE.equals(config.getClientFlag())) {
-                if (ObjUtil.isNull(reqVO.getClientId())) {
-                    throw exception(WM_BATCH_CLIENT_REQUIRED);
-                }
-                batch.setClientId(reqVO.getClientId());
+            if (Boolean.TRUE.equals(config.getClientFlag()) && ObjUtil.isNull(reqVO.getClientId())) {
+                throw exception(WM_BATCH_CLIENT_REQUIRED);
             }
             // 閲囪喘璁㈠崟缂栧彿
-            if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag())) {
-                if (ObjUtil.isNull(reqVO.getPurchaseOrderCode())) {
-                    throw exception(WM_BATCH_PURCHASE_ORDER_CODE_REQUIRED);
-                }
-                batch.setPurchaseOrderCode(reqVO.getPurchaseOrderCode());
+            if (Boolean.TRUE.equals(config.getPurchaseOrderCodeFlag()) && ObjUtil.isNull(reqVO.getPurchaseOrderCode())) {
+                throw exception(WM_BATCH_PURCHASE_ORDER_CODE_REQUIRED);
             }
             // 閿�鍞鍗曠紪鍙�
-            if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag())) {
-                if (ObjUtil.isNull(reqVO.getSalesOrderCode())) {
-                    throw exception(WM_BATCH_CUSTOMER_ORDER_CODE_REQUIRED);
-                }
-                batch.setSalesOrderCode(reqVO.getSalesOrderCode());
+            if (Boolean.TRUE.equals(config.getSalesOrderCodeFlag()) && ObjUtil.isNull(reqVO.getSalesOrderCode())) {
+                throw exception(WM_BATCH_CUSTOMER_ORDER_CODE_REQUIRED);
             }
             // 鐢熶骇宸ュ崟
-            if (Boolean.TRUE.equals(config.getWorkOrderFlag())) {
-                if (ObjUtil.isNull(reqVO.getWorkOrderId())) {
-                    throw exception(WM_BATCH_WORK_ORDER_REQUIRED);
-                }
-                batch.setWorkOrderId(reqVO.getWorkOrderId());
+            if (Boolean.TRUE.equals(config.getWorkOrderFlag()) && ObjUtil.isNull(reqVO.getWorkOrderId())) {
+                throw exception(WM_BATCH_WORK_ORDER_REQUIRED);
             }
             // 鐢熶骇浠诲姟
-            if (Boolean.TRUE.equals(config.getTaskFlag())) {
-                if (ObjUtil.isNull(reqVO.getTaskId())) {
-                    throw exception(WM_BATCH_TASK_REQUIRED);
-                }
-                batch.setTaskId(reqVO.getTaskId());
+            if (Boolean.TRUE.equals(config.getTaskFlag()) && ObjUtil.isNull(reqVO.getTaskId())) {
+                throw exception(WM_BATCH_TASK_REQUIRED);
             }
             // 宸ヤ綔绔�
-            if (Boolean.TRUE.equals(config.getWorkstationFlag())) {
-                if (ObjUtil.isNull(reqVO.getWorkstationId())) {
-                    throw exception(WM_BATCH_WORKSTATION_REQUIRED);
-                }
-                batch.setWorkstationId(reqVO.getWorkstationId());
+            if (Boolean.TRUE.equals(config.getWorkstationFlag()) && ObjUtil.isNull(reqVO.getWorkstationId())) {
+                throw exception(WM_BATCH_WORKSTATION_REQUIRED);
             }
             // 宸ュ叿
-            if (Boolean.TRUE.equals(config.getToolFlag())) {
-                if (ObjUtil.isNull(reqVO.getToolId())) {
-                    throw exception(WM_BATCH_TOOL_REQUIRED);
-                }
-                batch.setToolId(reqVO.getToolId());
+            if (Boolean.TRUE.equals(config.getToolFlag()) && ObjUtil.isNull(reqVO.getToolId())) {
+                throw exception(WM_BATCH_TOOL_REQUIRED);
             }
             // 妯″叿
-            if (Boolean.TRUE.equals(config.getMoldFlag())) {
-                if (ObjUtil.isNull(reqVO.getMoldId())) {
-                    throw exception(WM_BATCH_MOLD_REQUIRED);
-                }
-                batch.setMoldId(reqVO.getMoldId());
+            if (Boolean.TRUE.equals(config.getMoldFlag()) && ObjUtil.isNull(reqVO.getMoldId())) {
+                throw exception(WM_BATCH_MOLD_REQUIRED);
             }
             // 鐢熶骇鎵瑰彿
-            if (Boolean.TRUE.equals(config.getLotNumberFlag())) {
-                if (ObjUtil.isNull(reqVO.getLotNumber())) {
-                    throw exception(WM_BATCH_LOT_NUMBER_REQUIRED);
-                }
-                batch.setLotNumber(reqVO.getLotNumber());
+            if (Boolean.TRUE.equals(config.getLotNumberFlag()) && ObjUtil.isNull(reqVO.getLotNumber())) {
+                throw exception(WM_BATCH_LOT_NUMBER_REQUIRED);
             }
             // 璐ㄩ噺鐘舵��
-            if (Boolean.TRUE.equals(config.getQualityStatusFlag())) {
-                if (ObjUtil.isNull(reqVO.getQualityStatus())) {
-                    throw exception(WM_BATCH_QUALITY_STATUS_REQUIRED);
-                }
-                batch.setQualityStatus(reqVO.getQualityStatus());
+            if (Boolean.TRUE.equals(config.getQualityStatusFlag()) && ObjUtil.isNull(reqVO.getQualityStatus())) {
+                throw exception(WM_BATCH_QUALITY_STATUS_REQUIRED);
             }
         }
-        // 鏃犳壒娆¢厤缃椂锛屼娇鐢ㄤ紶鍏ョ殑鍙傛暟锛堝鏉炬ā寮忥紝閫傜敤浜庤嚜瀹氫箟鍏ュ簱绛夊満鏅級
-        else {
-            if (reqVO.getProduceDate() != null) {
-                batch.setProduceDate(reqVO.getProduceDate());
-            }
-            if (reqVO.getReceiptDate() != null) {
-                batch.setReceiptDate(reqVO.getReceiptDate());
-            }
-            if (reqVO.getExpireDate() != null) {
-                batch.setExpireDate(reqVO.getExpireDate());
-            }
-            if (reqVO.getVendorId() != null) {
-                batch.setVendorId(reqVO.getVendorId());
-            }
-            if (reqVO.getClientId() != null) {
-                batch.setClientId(reqVO.getClientId());
-            }
+
+        // ========== 淇濆瓨瀛楁锛堟棤璁烘湁鏃犻厤缃紝鍙璋冪敤鏂逛紶浜嗗氨淇濆瓨锛� ==========
+        if (reqVO.getProduceDate() != null) {
+            batch.setProduceDate(reqVO.getProduceDate());
+        }
+        if (reqVO.getReceiptDate() != null) {
+            batch.setReceiptDate(reqVO.getReceiptDate());
+        }
+        if (reqVO.getExpireDate() != null) {
+            batch.setExpireDate(reqVO.getExpireDate());
+        }
+        if (reqVO.getVendorId() != null) {
+            batch.setVendorId(reqVO.getVendorId());
+        }
+        if (reqVO.getClientId() != null) {
+            batch.setClientId(reqVO.getClientId());
+        }
+        if (reqVO.getPurchaseOrderCode() != null) {
+            batch.setPurchaseOrderCode(reqVO.getPurchaseOrderCode());
+        }
+        if (reqVO.getSalesOrderCode() != null) {
+            batch.setSalesOrderCode(reqVO.getSalesOrderCode());
+        }
+        if (reqVO.getWorkOrderId() != null) {
+            batch.setWorkOrderId(reqVO.getWorkOrderId());
+        }
+        if (reqVO.getTaskId() != null) {
+            batch.setTaskId(reqVO.getTaskId());
+        }
+        if (reqVO.getWorkstationId() != null) {
+            batch.setWorkstationId(reqVO.getWorkstationId());
+        }
+        if (reqVO.getToolId() != null) {
+            batch.setToolId(reqVO.getToolId());
+        }
+        if (reqVO.getMoldId() != null) {
+            batch.setMoldId(reqVO.getMoldId());
+        }
+        if (reqVO.getLotNumber() != null) {
+            batch.setLotNumber(reqVO.getLotNumber());
+        }
+        if (reqVO.getQualityStatus() != null) {
+            batch.setQualityStatus(reqVO.getQualityStatus());
         }
 
         // 3.1 鎯呭喌涓�锛氫紶鍏ヤ簡 batchCode锛屾煡璇㈡槸鍚﹀瓨鍦ㄨ鎵规鍙�
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
index dd02774..9e7b9be 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.srm.service.supplier;
 
+import cn.hutool.core.collection.CollUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.srm.controller.admin.supplier.vo.SrmSupplierPageReqVO;
@@ -14,6 +15,8 @@
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+
 import cn.hutool.core.collection.CollUtil;
 import java.util.Collection;
 import java.util.Collections;

--
Gitblit v1.9.3