From 8064a77af2faf5bf080be0c4347a3a975225453b Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期二, 14 七月 2026 15:38:12 +0800
Subject: [PATCH] 银川 1.物料管理页面批次属性功能联调 2.库存现有量添加自定义出库入库功能

---
 src/views/wls/materialstock/components/batch-config-display.vue |   96 ++
 src/views/wls/salesnotice/index.vue                             |   28 
 src/views/wls/materialstock/data.ts                             |   70 +
 src/views/wls/materialstock/components/stock-adjust-modal.vue   |  488 +++++++++++++
 src/views/basicData/mdm/components/data.ts                      |  111 +++
 src/api/mes/wm/salesnotice/index.ts                             |   16 
 src/views/erp/sale/order/components/index.ts                    |    2 
 src/views/wls/materialstock/components/index.ts                 |    1 
 src/api/system/tenant-package/index.ts                          |   84 +
 src/views/basicData/mdm/components/batch-config.vue             |  147 +++
 src/views/basicData/mdm/components/select-dialog.vue            |  209 +++++
 src/api/core/auth.ts                                            |   16 
 src/views/basicData/mdm/components/select.vue                   |  138 +++
 src/views/wls/salesnotice/data.ts                               |   83 ++
 src/packages/constants/src/biz-mes-enum.ts                      |    7 
 src/views/basicData/mdm/components/index.ts                     |    2 
 src/views/basicData/mdm/modules/form.vue                        |   74 +
 src/views/wls/materialstock/index.vue                           |   24 
 src/views/erp/sale/order/data.ts                                |    2 
 src/views/erp/sale/order/components/select.vue                  |  135 +++
 src/views/erp/sale/order/index.vue                              |   27 
 src/api/mes/wm/productsales/line/index.ts                       |    1 
 .env.production                                                 |    4 
 src/views/basicData/mdm/data.ts                                 |   10 
 src/api/mes/md/item-batch-config/index.ts                       |   36 
 src/api/mes/wm/materialstock/index.ts                           |   60 +
 src/api/mes/wm/salesnotice/line/index.ts                        |    2 
 src/views/erp/sale/order/components/select-dialog.vue           |  199 +++++
 src/api/system/tenant/index.ts                                  |  100 +
 29 files changed, 2,078 insertions(+), 94 deletions(-)

diff --git a/.env.production b/.env.production
index a8f3d29..cd77149 100644
--- a/.env.production
+++ b/.env.production
@@ -1,9 +1,9 @@
 VITE_BASE=/
 
 # 璇锋眰璺緞
-VITE_BASE_URL=http://127.0.0.1:48080
+VITE_BASE_URL=http://36.213.180.217:9500
 # 鎺ュ彛鍦板潃
-VITE_GLOB_API_URL=http://127.0.0.1:48080/admin-api
+VITE_GLOB_API_URL=http://36.213.180.217:9500/admin-api
 # 鏂囦欢涓婁紶绫诲瀷锛歴erver - 鍚庣涓婁紶锛� client - 鍓嶇鐩磋繛涓婁紶锛屼粎鏀寔S3鏈嶅姟
 VITE_UPLOAD_TYPE=server
 
diff --git a/src/api/core/auth.ts b/src/api/core/auth.ts
index 604644a..cec93f5 100644
--- a/src/api/core/auth.ts
+++ b/src/api/core/auth.ts
@@ -100,16 +100,20 @@
 
 /** 鑾峰彇绉熸埛鍒楄〃 */
 export async function getTenantSimpleList() {
-  return requestClient.get<AuthApi.TenantResult[]>(
-    `/system/tenant/simple-list`,
-  );
+  // 绉熸埛鎺ュ彛宸茬鐢紝杩斿洖绌烘暟缁�
+  return Promise.resolve([]);
+  // return requestClient.get<AuthApi.TenantResult[]>(
+  //   `/system/tenant/simple-list`,
+  // );
 }
 
 /** 浣跨敤绉熸埛鍩熷悕锛岃幏寰楃鎴蜂俊鎭� */
 export async function getTenantByWebsite(website: string) {
-  return requestClient.get<AuthApi.TenantResult>(
-    `/system/tenant/get-by-website?website=${website}`,
-  );
+  // 绉熸埛鎺ュ彛宸茬鐢紝杩斿洖绌哄璞�
+  return Promise.resolve({} as AuthApi.TenantResult);
+  // return requestClient.get<AuthApi.TenantResult>(
+  //   `/system/tenant/get-by-website?website=${website}`,
+  // );
 }
 
 /** 鑾峰彇楠岃瘉鐮� */
diff --git a/src/api/mes/md/item-batch-config/index.ts b/src/api/mes/md/item-batch-config/index.ts
new file mode 100644
index 0000000..c67d170
--- /dev/null
+++ b/src/api/mes/md/item-batch-config/index.ts
@@ -0,0 +1,36 @@
+import type { PageParam, PageResult } from '#/packages/effects/request/src';
+
+import { requestClient } from '#/api/request';
+
+export namespace MdmItemBatchConfigApi {
+  /** 鐗╂枡鎵规閰嶇疆锛堜粎淇濈暀7涓瓧娈碉級 */
+  export interface ItemBatchConfig {
+    id?: number; // 閰嶇疆ID
+    itemId: number; // 鐗╂枡ID
+    produceDateFlag?: boolean; // 鐢熶骇鏃ユ湡蹇呭~
+    expireDateFlag?: boolean; // 鏈夋晥鏈熷繀濉�
+    receiptDateFlag?: boolean; // 鍏ュ簱鏃ユ湡蹇呭~
+    vendorFlag?: boolean; // 渚涘簲鍟嗗繀濉�
+    purchaseOrderCodeFlag?: boolean; // 閲囪喘璁㈠崟鍙峰繀濉�
+    lotNumberFlag?: boolean; // 鐢熶骇鎵瑰彿蹇呭~
+    qualityStatusFlag?: boolean; // 璐ㄩ噺鐘舵�佸繀濉�
+  }
+}
+
+/** 鏍规嵁鐗╂枡ID鑾峰彇鎵规閰嶇疆 */
+export function getItemBatchConfigByItemId(itemId: number) {
+  return requestClient.get<MdmItemBatchConfigApi.ItemBatchConfig | null>(
+    '/mes/md/item-batch-config/get-by-item-id',
+    { params: { itemId } },
+  );
+}
+
+/** 淇濆瓨鎵规閰嶇疆锛堟柊澧�/鏇存柊锛� */
+export function saveItemBatchConfig(data: MdmItemBatchConfigApi.ItemBatchConfig) {
+  return requestClient.post<number>('/mes/md/item-batch-config/save', data);
+}
+
+/** 鍒犻櫎鎵规閰嶇疆 */
+export function deleteItemBatchConfig(id: number) {
+  return requestClient.delete(`/mes/md/item-batch-config/delete?id=${id}`);
+}
diff --git a/src/api/mes/wm/materialstock/index.ts b/src/api/mes/wm/materialstock/index.ts
index 49aab96..02c3e94 100644
--- a/src/api/mes/wm/materialstock/index.ts
+++ b/src/api/mes/wm/materialstock/index.ts
@@ -24,6 +24,10 @@
     vendorId?: number; // 渚涘簲鍟嗙紪鍙�
     vendorName?: string; // 渚涘簲鍟嗗悕绉�
     quantity?: number; // 鍦ㄥ簱鏁伴噺
+    frozenQuantity?: number; // 鍐荤粨鏁伴噺
+    reservedQuantity?: number; // 鍗犵敤閲�
+    inTransitQuantity?: number; // 鍦ㄩ�旈噺
+    availableQuantity?: number; // 鍙敤閲�
     receiptTime?: string; // 鍏ュ簱鏃ユ湡
     frozen?: boolean; // 鏄惁鍐荤粨
     createTime?: Date; // 鍒涘缓鏃堕棿
@@ -59,3 +63,59 @@
     params,
   });
 }
+
+/** 搴撳瓨璋冩暣锛堣嚜瀹氫箟鍑哄叆搴擄級- 鍏ュ簱 */
+export interface MaterialStockAdjustInParams {
+  itemId: number; // 鐗╂枡ID
+  warehouseId: number; // 浠撳簱ID
+  locationId: number; // 搴撳尯ID
+  areaId: number; // 搴撲綅ID
+  batchId?: number; // 鎵规ID锛堥�夋嫨宸叉湁鎵规鏃朵紶鍏ワ級
+  batchCode?: string; // 鎵规鍙凤紙鏂板缓鎵规鏃朵紶鍏ワ級
+  produceDate?: string; // 鐢熶骇鏃ユ湡锛堟柊寤烘壒娆℃椂浼犲叆锛�
+  expireDate?: string; // 鏈夋晥鏈燂紙鏂板缓鎵规鏃朵紶鍏ワ級
+  vendorId?: number; // 渚涘簲鍟咺D
+  quantity: number; // 鍏ュ簱鏁伴噺锛堝繀椤诲ぇ浜�0锛�
+  reason?: string; // 鍏ュ簱鍘熷洜
+}
+
+/** 搴撳瓨璋冩暣锛堣嚜瀹氫箟鍑哄叆搴擄級- 鍑哄簱 */
+export interface MaterialStockAdjustOutParams {
+  itemId: number; // 鐗╂枡ID
+  warehouseId: number; // 浠撳簱ID
+  locationId: number; // 搴撳尯ID
+  areaId: number; // 搴撲綅ID
+  batchId?: number; // 鎵规ID
+  batchCode?: string; // 鎵规鍙�
+  vendorId?: number; // 渚涘簲鍟咺D
+  quantity: number; // 鍑哄簱鏁伴噺锛堝繀椤诲ぇ浜�0锛�
+  reason?: string; // 鍑哄簱鍘熷洜
+}
+
+/** 鑷畾涔夊叆搴� */
+export function adjustMaterialStockIn(data: MaterialStockAdjustInParams) {
+  return requestClient.post<number>('/mes/wm/material-stock/adjust-in', data);
+}
+
+/** 鑷畾涔夊嚭搴� */
+export function adjustMaterialStockOut(data: MaterialStockAdjustOutParams) {
+  return requestClient.post<number>('/mes/wm/material-stock/adjust-out', data);
+}
+
+/** 搴撳瓨璋冩暣锛堟鏁�=鍏ュ簱锛岃礋鏁�=鍑哄簱锛�- 鏃ф帴鍙o紝淇濈暀鍏煎 */
+export interface MaterialStockAdjustParams {
+  itemId: number; // 鐗╂枡ID
+  warehouseId: number; // 浠撳簱ID
+  locationId: number; // 搴撳尯ID
+  areaId: number; // 搴撲綅ID
+  batchId?: number; // 鎵规ID
+  batchCode?: string; // 鎵规鍙�
+  vendorId?: number; // 渚涘簲鍟咺D
+  quantity: number; // 璋冩暣鏁伴噺锛堟鏁�=鍏ュ簱锛岃礋鏁�=鍑哄簱锛�
+  reason?: string; // 璋冩暣鍘熷洜
+}
+
+/** 搴撳瓨璋冩暣锛堟鏁�=鍏ュ簱锛岃礋鏁�=鍑哄簱锛� - 鏃ф帴鍙o紝淇濈暀鍏煎 */
+export function adjustMaterialStock(data: MaterialStockAdjustParams) {
+  return requestClient.post<number>('/mes/wm/material-stock/adjust', data);
+}
diff --git a/src/api/mes/wm/productsales/line/index.ts b/src/api/mes/wm/productsales/line/index.ts
index f1d4ff5..1f8e46a 100644
--- a/src/api/mes/wm/productsales/line/index.ts
+++ b/src/api/mes/wm/productsales/line/index.ts
@@ -8,6 +8,7 @@
     id?: number; // 琛岀紪鍙�
     salesId?: number; // 鍑哄簱鍗曠紪鍙�
     noticeLineId?: number; // 鍙戣揣閫氱煡鍗曡缂栧彿
+    saleOrderItemId?: number; // 閿�鍞鍗曟槑缁咺D锛堢敤浜庤拷婧級
     itemId?: number; // 鐗╂枡缂栧彿
     itemCode?: string; // 鐗╂枡缂栫爜
     itemName?: string; // 鐗╂枡鍚嶇О
diff --git a/src/api/mes/wm/salesnotice/index.ts b/src/api/mes/wm/salesnotice/index.ts
index c053f69..6bb0ab8 100644
--- a/src/api/mes/wm/salesnotice/index.ts
+++ b/src/api/mes/wm/salesnotice/index.ts
@@ -8,6 +8,7 @@
     id?: number; // 閫氱煡鍗曠紪鍙�
     code?: string; // 閫氱煡鍗曠紪鍙�
     name?: string; // 閫氱煡鍗曞悕绉�
+    saleOrderId?: number; // 閿�鍞鍗曠紪鍙�
     salesOrderCode?: string; // 閿�鍞鍗曠紪鍙�
     clientId?: number; // 瀹㈡埛缂栧彿
     clientCode?: string; // 瀹㈡埛缂栫爜
@@ -17,6 +18,7 @@
     recipientTelephone?: string; // 鑱旂郴鏂瑰紡
     recipientAddress?: string; // 鏀惰揣鍦板潃
     status?: number; // 鍗曟嵁鐘舵��
+    outStatus?: number; // 鍑哄簱鐘舵�侊細0-鏈嚭搴擄紝1-閮ㄥ垎鍑哄簱锛�2-鍏ㄩ儴鍑哄簱
     remark?: string; // 澶囨敞
     createTime?: Date; // 鍒涘缓鏃堕棿
   }
@@ -63,3 +65,17 @@
     params,
   });
 }
+
+/** 浠庨攢鍞鍗曠敓鎴愬彂璐ч�氱煡鍗� */
+export function generateFromSaleOrder(saleOrderId: number) {
+  return requestClient.post<number>('/mes/wm/sales-notice/generate-from-sale-order', {
+    saleOrderId,
+  });
+}
+
+/** 浠庡彂璐ч�氱煡鍗曠敓鎴愰攢鍞嚭搴撳崟 */
+export function generateProductSales(id: number) {
+  return requestClient.post<number>('/mes/wm/sales-notice/generate-product-sales', null, {
+    params: { id },
+  });
+}
diff --git a/src/api/mes/wm/salesnotice/line/index.ts b/src/api/mes/wm/salesnotice/line/index.ts
index 948d1bc..cb908e7 100644
--- a/src/api/mes/wm/salesnotice/line/index.ts
+++ b/src/api/mes/wm/salesnotice/line/index.ts
@@ -7,6 +7,7 @@
   export interface SalesNoticeLine {
     id?: number; // 琛岀紪鍙�
     noticeId?: number; // 鍙戣揣閫氱煡鍗曠紪鍙�
+    saleOrderItemId?: number; // 閿�鍞鍗曟槑缁咺D锛堢敤浜庤拷婧級
     itemId?: number; // 鐗╂枡缂栧彿
     itemCode?: string; // 鐗╂枡缂栫爜
     itemName?: string; // 鐗╂枡鍚嶇О
@@ -15,6 +16,7 @@
     batchId?: number; // 鎵规缂栧彿
     batchCode?: string; // 鎵规鍙�
     quantity?: number; // 鍙戣揣鏁伴噺
+    outCount?: number; // 宸插嚭搴撴暟閲�
     oqcCheckFlag?: boolean; // 鏄惁妫�楠�
     remark?: string; // 澶囨敞
   }
diff --git a/src/api/system/tenant-package/index.ts b/src/api/system/tenant-package/index.ts
index 53be78f..532e14c 100644
--- a/src/api/system/tenant-package/index.ts
+++ b/src/api/system/tenant-package/index.ts
@@ -17,48 +17,78 @@
   }
 }
 
-/** 绉熸埛濂楅鍒楄〃 */
+// 绉熸埛濂楅鎺ュ彛宸茬鐢�
+// /** 绉熸埛濂楅鍒楄〃 */
+// export function getTenantPackagePage(params: PageParam) {
+//   return requestClient.get<PageResult<SystemTenantPackageApi.TenantPackage>>(
+//     '/system/tenant-package/page',
+//     { params },
+//   );
+// }
+
+// /** 鏌ヨ绉熸埛濂楅璇︽儏 */
+// export function getTenantPackage(id: number) {
+//   return requestClient.get(`/system/tenant-package/get?id=${id}`);
+// }
+
+// /** 鏂板绉熸埛濂楅 */
+// export function createTenantPackage(
+//   data: SystemTenantPackageApi.TenantPackage,
+// ) {
+//   return requestClient.post('/system/tenant-package/create', data);
+// }
+
+// /** 淇敼绉熸埛濂楅 */
+// export function updateTenantPackage(
+//   data: SystemTenantPackageApi.TenantPackage,
+// ) {
+//   return requestClient.put('/system/tenant-package/update', data);
+// }
+
+// /** 鍒犻櫎绉熸埛濂楅 */
+// export function deleteTenantPackage(id: number) {
+//   return requestClient.delete(`/system/tenant-package/delete?id=${id}`);
+// }
+
+// /** 鎵归噺鍒犻櫎绉熸埛濂楅 */
+// export function deleteTenantPackageList(ids: number[]) {
+//   return requestClient.delete(
+//     `/system/tenant-package/delete-list?ids=${ids.join(',')}`,
+//   );
+// }
+
+// /** 鑾峰彇绉熸埛濂楅绮剧畝淇℃伅鍒楄〃 */
+// export function getTenantPackageList() {
+//   return requestClient.get<SystemTenantPackageApi.TenantPackage[]>(
+//     '/system/tenant-package/get-simple-list',
+//   );
+// }
+
+// 鎻愪緵绌哄嚱鏁颁互閬垮厤寮曠敤閿欒
 export function getTenantPackagePage(params: PageParam) {
-  return requestClient.get<PageResult<SystemTenantPackageApi.TenantPackage>>(
-    '/system/tenant-package/page',
-    { params },
-  );
+  return Promise.resolve({ list: [], total: 0 });
 }
 
-/** 鏌ヨ绉熸埛濂楅璇︽儏 */
 export function getTenantPackage(id: number) {
-  return requestClient.get(`/system/tenant-package/get?id=${id}`);
+  return Promise.resolve({} as SystemTenantPackageApi.TenantPackage);
 }
 
-/** 鏂板绉熸埛濂楅 */
-export function createTenantPackage(
-  data: SystemTenantPackageApi.TenantPackage,
-) {
-  return requestClient.post('/system/tenant-package/create', data);
+export function createTenantPackage(data: SystemTenantPackageApi.TenantPackage) {
+  return Promise.resolve({});
 }
 
-/** 淇敼绉熸埛濂楅 */
-export function updateTenantPackage(
-  data: SystemTenantPackageApi.TenantPackage,
-) {
-  return requestClient.put('/system/tenant-package/update', data);
+export function updateTenantPackage(data: SystemTenantPackageApi.TenantPackage) {
+  return Promise.resolve({});
 }
 
-/** 鍒犻櫎绉熸埛濂楅 */
 export function deleteTenantPackage(id: number) {
-  return requestClient.delete(`/system/tenant-package/delete?id=${id}`);
+  return Promise.resolve({});
 }
 
-/** 鎵归噺鍒犻櫎绉熸埛濂楅 */
 export function deleteTenantPackageList(ids: number[]) {
-  return requestClient.delete(
-    `/system/tenant-package/delete-list?ids=${ids.join(',')}`,
-  );
+  return Promise.resolve({});
 }
 
-/** 鑾峰彇绉熸埛濂楅绮剧畝淇℃伅鍒楄〃 */
 export function getTenantPackageList() {
-  return requestClient.get<SystemTenantPackageApi.TenantPackage[]>(
-    '/system/tenant-package/get-simple-list',
-  );
+  return Promise.resolve([]);
 }
diff --git a/src/api/system/tenant/index.ts b/src/api/system/tenant/index.ts
index 6ee52e1..e2a83e2 100644
--- a/src/api/system/tenant/index.ts
+++ b/src/api/system/tenant/index.ts
@@ -17,60 +17,98 @@
   }
 }
 
-/** 绉熸埛鍒楄〃 */
+// 绉熸埛鎺ュ彛宸茬鐢�
+// /** 绉熸埛鍒楄〃 */
+// export function getTenantPage(params: PageParam) {
+//   return requestClient.get<PageResult<SystemTenantApi.Tenant>>(
+//     '/system/tenant/page',
+//     { params },
+//   );
+// }
+
+// /** 鑾峰彇绉熸埛绮剧畝淇℃伅鍒楄〃 */
+// export function getSimpleTenantList() {
+//   return requestClient.get<SystemTenantApi.Tenant[]>(
+//     '/system/tenant/simple-list',
+//   );
+// }
+
+// /** 鏌ヨ绉熸埛璇︽儏 */
+// export function getTenant(id: number) {
+//   return requestClient.get<SystemTenantApi.Tenant>(
+//     `/system/tenant/get?id=${id}`,
+//   );
+// }
+
+// /** 鑾峰彇绉熸埛绮剧畝淇℃伅鍒楄〃 */
+// export function getTenantList() {
+//   return requestClient.get<SystemTenantApi.Tenant[]>(
+//     '/system/tenant/simple-list',
+//   );
+// }
+
+// /** 鏂板绉熸埛 */
+// export function createTenant(data: SystemTenantApi.Tenant) {
+//   return requestClient.post('/system/tenant/create', data);
+// }
+
+// /** 淇敼绉熸埛 */
+// export function updateTenant(data: SystemTenantApi.Tenant) {
+//   return requestClient.put('/system/tenant/update', data);
+// }
+
+// /** 鍒犻櫎绉熸埛 */
+// export function deleteTenant(id: number) {
+//   return requestClient.delete(`/system/tenant/delete?id=${id}`);
+// }
+
+// /** 鎵归噺鍒犻櫎绉熸埛 */
+// export function deleteTenantList(ids: number[]) {
+//   return requestClient.delete(
+//     `/system/tenant/delete-list?ids=${ids.join(',')}`,
+//   );
+// }
+
+// /** 瀵煎嚭绉熸埛 */
+// export function exportTenant(params: any) {
+//   return requestClient.download('/system/tenant/export-excel', {
+//     params,
+//   });
+// }
+
+// 鎻愪緵绌哄嚱鏁颁互閬垮厤寮曠敤閿欒
 export function getTenantPage(params: PageParam) {
-  return requestClient.get<PageResult<SystemTenantApi.Tenant>>(
-    '/system/tenant/page',
-    { params },
-  );
+  return Promise.resolve({ list: [], total: 0 });
 }
 
-/** 鑾峰彇绉熸埛绮剧畝淇℃伅鍒楄〃 */
 export function getSimpleTenantList() {
-  return requestClient.get<SystemTenantApi.Tenant[]>(
-    '/system/tenant/simple-list',
-  );
+  return Promise.resolve([]);
 }
 
-/** 鏌ヨ绉熸埛璇︽儏 */
 export function getTenant(id: number) {
-  return requestClient.get<SystemTenantApi.Tenant>(
-    `/system/tenant/get?id=${id}`,
-  );
+  return Promise.resolve({} as SystemTenantApi.Tenant);
 }
 
-/** 鑾峰彇绉熸埛绮剧畝淇℃伅鍒楄〃 */
 export function getTenantList() {
-  return requestClient.get<SystemTenantApi.Tenant[]>(
-    '/system/tenant/simple-list',
-  );
+  return Promise.resolve([]);
 }
 
-/** 鏂板绉熸埛 */
 export function createTenant(data: SystemTenantApi.Tenant) {
-  return requestClient.post('/system/tenant/create', data);
+  return Promise.resolve({});
 }
 
-/** 淇敼绉熸埛 */
 export function updateTenant(data: SystemTenantApi.Tenant) {
-  return requestClient.put('/system/tenant/update', data);
+  return Promise.resolve({});
 }
 
-/** 鍒犻櫎绉熸埛 */
 export function deleteTenant(id: number) {
-  return requestClient.delete(`/system/tenant/delete?id=${id}`);
+  return Promise.resolve({});
 }
 
-/** 鎵归噺鍒犻櫎绉熸埛 */
 export function deleteTenantList(ids: number[]) {
-  return requestClient.delete(
-    `/system/tenant/delete-list?ids=${ids.join(',')}`,
-  );
+  return Promise.resolve({});
 }
 
-/** 瀵煎嚭绉熸埛 */
 export function exportTenant(params: any) {
-  return requestClient.download('/system/tenant/export-excel', {
-    params,
-  });
+  return Promise.resolve(new Blob());
 }
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index c790052..0a1b051 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -371,6 +371,13 @@
   APPROVED: MesOrderStatusConstants.APPROVED,
 } as const;
 
+/** MES 鍙戣揣閫氱煡鍗曞嚭搴撶姸鎬佹灇涓� */
+export const MesWmSalesNoticeOutStatusEnum = {
+  NONE: 0,
+  PARTIAL: 1,
+  ALL: 2,
+} as const;
+
 /** MES 閿�鍞��璐у崟鐘舵�佹灇涓� */
 export const MesWmReturnSalesStatusEnum = {
   PREPARE: MesOrderStatusConstants.DRAFT,
diff --git a/src/views/basicData/mdm/components/batch-config.vue b/src/views/basicData/mdm/components/batch-config.vue
new file mode 100644
index 0000000..714bef4
--- /dev/null
+++ b/src/views/basicData/mdm/components/batch-config.vue
@@ -0,0 +1,147 @@
+<script lang="ts" setup>
+import type { MdmItemBatchConfigApi } from '#/api/mes/md/item-batch-config';
+
+import { computed, ref, watch } from 'vue';
+
+import { Checkbox, Divider } from 'ant-design-vue';
+
+import {
+  getItemBatchConfigByItemId,
+  saveItemBatchConfig,
+} from '#/api/mes/md/item-batch-config';
+
+const props = defineProps<{
+  itemId?: number;
+  isBatchManaged?: boolean;
+}>();
+
+const emit = defineEmits<{
+  change: [config: Partial<MdmItemBatchConfigApi.ItemBatchConfig>];
+}>();
+
+// 鎵规閰嶇疆鏁版嵁
+const config = ref<Partial<MdmItemBatchConfigApi.ItemBatchConfig>>({
+  produceDateFlag: false,
+  expireDateFlag: false,
+  receiptDateFlag: false,
+  vendorFlag: false,
+  purchaseOrderCodeFlag: false,
+  lotNumberFlag: false,
+  qualityStatusFlag: false,
+});
+
+// 鏄惁鍔犺浇涓�
+const loading = ref(false);
+
+// 鏄惁鏄剧ず閰嶇疆锛堝惎鐢ㄦ壒娆$鐞嗘椂鏄剧ず锛�
+const visible = computed(() => props.isBatchManaged === true);
+
+// 鎵规閰嶇疆閫夐」锛堜粎淇濈暀7涓瓧娈碉級
+const configOptions = [
+  { key: 'produceDateFlag', label: '鐢熶骇鏃ユ湡' },
+  { key: 'expireDateFlag', label: '鏈夋晥鏈�' },
+  { key: 'receiptDateFlag', label: '鍏ュ簱鏃ユ湡' },
+  { key: 'vendorFlag', label: '渚涘簲鍟�' },
+  { key: 'purchaseOrderCodeFlag', label: '閲囪喘璁㈠崟鍙�' },
+  { key: 'lotNumberFlag', label: '鐢熶骇鎵瑰彿' },
+  { key: 'qualityStatusFlag', label: '璐ㄩ噺鐘舵��' },
+] as const;
+
+/** 鍔犺浇鎵规閰嶇疆 */
+async function loadConfig() {
+  if (!props.itemId || !props.isBatchManaged) {
+    return;
+  }
+  loading.value = true;
+  try {
+    const data = await getItemBatchConfigByItemId(props.itemId);
+    if (data) {
+      config.value = { ...config.value, ...data };
+    }
+  } catch {
+    // 鏃犻厤缃椂涓嶆姤閿�
+  } finally {
+    loading.value = false;
+  }
+}
+
+/** 淇濆瓨鎵规閰嶇疆 */
+async function saveConfig(): Promise<number | undefined> {
+  if (!props.itemId || !props.isBatchManaged) {
+    return undefined;
+  }
+  const data: MdmItemBatchConfigApi.ItemBatchConfig = {
+    ...config.value as MdmItemBatchConfigApi.ItemBatchConfig,
+    itemId: props.itemId,
+  };
+  const id = await saveItemBatchConfig(data);
+  config.value.id = id;
+  return id;
+}
+
+/** 鑾峰彇褰撳墠閰嶇疆 */
+function getConfig(): Partial<MdmItemBatchConfigApi.ItemBatchConfig> {
+  return { ...config.value };
+}
+
+/** 閲嶇疆閰嶇疆 */
+function resetConfig() {
+  config.value = {
+    produceDateFlag: false,
+    expireDateFlag: false,
+    receiptDateFlag: false,
+    vendorFlag: false,
+    purchaseOrderCodeFlag: false,
+    lotNumberFlag: false,
+    qualityStatusFlag: false,
+  };
+}
+
+// 鐩戝惉 itemId 鍙樺寲锛屽姞杞介厤缃�
+watch(
+  () => [props.itemId, props.isBatchManaged],
+  () => {
+    if (props.itemId && props.isBatchManaged) {
+      loadConfig();
+    } else {
+      resetConfig();
+    }
+  },
+  { immediate: true },
+);
+
+// 鐩戝惉閰嶇疆鍙樺寲锛岃Е鍙� change 浜嬩欢
+watch(
+  config,
+  () => {
+    emit('change', getConfig());
+  },
+  { deep: true },
+);
+
+defineExpose({
+  saveConfig,
+  getConfig,
+  loadConfig,
+  resetConfig,
+});
+</script>
+
+<template>
+  <div v-if="visible" class="mt-4">
+    <Divider orientation="left">鎵规灞炴�ч厤缃�</Divider>
+    <div class="grid grid-cols-4 gap-4 px-4">
+      <Checkbox
+        v-for="option in configOptions"
+        :key="option.key"
+        v-model:checked="config[option.key]"
+        :disabled="loading"
+      >
+        {{ option.label }}
+      </Checkbox>
+    </div>
+    <p class="mt-2 px-4 text-xs text-gray-500">
+      鍕鹃�夎〃绀哄垱寤烘壒娆℃椂璇ュ瓧娈靛繀濉�
+    </p>
+  </div>
+</template>
diff --git a/src/views/basicData/mdm/components/data.ts b/src/views/basicData/mdm/components/data.ts
new file mode 100644
index 0000000..db1af68
--- /dev/null
+++ b/src/views/basicData/mdm/components/data.ts
@@ -0,0 +1,111 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MdmItemApi } from '#/api/mdm/item';
+
+import { h } from 'vue';
+import { Tag } from 'ant-design-vue';
+
+/** 鐗╂枡閫夋嫨寮圭獥鎼滅储琛ㄥ崟 */
+export function useMdmItemSelectGridFormSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'code',
+      label: '鐗╂枡缂栫爜',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欑紪鐮�',
+      },
+    },
+    {
+      fieldName: 'name',
+      label: '鐗╂枡鍚嶇О',
+      component: 'Input',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇疯緭鍏ョ墿鏂欏悕绉�',
+      },
+    },
+    {
+      fieldName: 'itemType',
+      label: '鐗╂枡绫诲瀷',
+      component: 'Select',
+      componentProps: {
+        allowClear: true,
+        placeholder: '璇烽�夋嫨鐗╂枡绫诲瀷',
+        options: [
+          { label: '鍘熸枡', value: 1 },
+          { label: '鍗婃垚鍝�', value: 2 },
+          { label: '鎴愬搧', value: 3 },
+          { label: '杈呮枡', value: 4 },
+        ],
+      },
+    },
+  ];
+}
+
+/** 鐗╂枡绫诲瀷棰滆壊鏄犲皠 */
+const itemTypeMap: Record<number, { label: string; color: string }> = {
+  1: { label: '鍘熸枡', color: 'green' },
+  2: { label: '鍗婃垚鍝�', color: 'orange' },
+  3: { label: '鎴愬搧', color: 'blue' },
+  4: { label: '杈呮枡', color: 'purple' },
+};
+
+/** 鐗╂枡閫夋嫨寮圭獥鍒楄〃瀛楁 */
+export function useMdmItemSelectGridColumns(
+  multiple = true,
+): VxeTableGridOptions<MdmItemApi.Item>['columns'] {
+  return [
+    { type: multiple ? 'checkbox' : 'radio', width: 50 },
+    {
+      field: 'code',
+      title: '鐗╂枡缂栫爜',
+      width: 180,
+    },
+    {
+      field: 'name',
+      title: '鐗╂枡鍚嶇О',
+      minWidth: 160,
+      align: 'left',
+    },
+    {
+      field: 'specification',
+      title: '瑙勬牸鍨嬪彿',
+      minWidth: 140,
+      align: 'left',
+    },
+    {
+      field: 'unitMeasureName',
+      title: '鍗曚綅',
+      width: 90,
+      align: 'center',
+    },
+    {
+      field: 'itemType',
+      title: '鐗╂枡绫诲瀷',
+      width: 110,
+      align: 'center',
+      formatter: ({ cellValue }: { cellValue: number }) => {
+        const type = itemTypeMap[cellValue];
+        return type ? type.label : '-';
+      },
+    },
+    {
+      field: 'isBatchManaged',
+      title: '鎵规绠$悊',
+      width: 110,
+      align: 'center',
+      cellRender: {
+        name: 'CellDict',
+        props: { type: 'infra_boolean_string' },
+      },
+    },
+    {
+      field: 'createTime',
+      title: '鍒涘缓鏃堕棿',
+      width: 180,
+      formatter: 'formatDateTime',
+    },
+  ];
+}
diff --git a/src/views/basicData/mdm/components/index.ts b/src/views/basicData/mdm/components/index.ts
new file mode 100644
index 0000000..662751f
--- /dev/null
+++ b/src/views/basicData/mdm/components/index.ts
@@ -0,0 +1,2 @@
+export { default as MdmItemSelect } from './select.vue';
+export { default as MdmItemSelectDialog } from './select-dialog.vue';
diff --git a/src/views/basicData/mdm/components/select-dialog.vue b/src/views/basicData/mdm/components/select-dialog.vue
new file mode 100644
index 0000000..9c7a48a
--- /dev/null
+++ b/src/views/basicData/mdm/components/select-dialog.vue
@@ -0,0 +1,209 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MdmItemApi } from '#/api/mdm/item';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getItemPage } from '#/api/mdm/item';
+
+import {
+  useMdmItemSelectGridColumns,
+  useMdmItemSelectGridFormSchema,
+} from './data';
+
+defineOptions({ name: 'MdmItemSelectDialog' });
+
+const emit = defineEmits<{
+  selected: [rows: MdmItemApi.Item[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(true); // 鏄惁澶氶��
+const selectedRows = ref<MdmItemApi.Item[]>([]); // 宸查�夌墿鏂欏垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夌墿鏂欑紪鍙峰垪琛�
+
+/** 鑾峰彇褰撳墠琛ㄦ牸鏁版嵁 */
+function getTableRows() {
+  return gridApi.grid.getTableData().fullData as MdmItemApi.Item[];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, MdmItemApi.Item>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as MdmItemApi.Item[];
+  records.forEach((row) => {
+    const rowId = row.id;
+    if (rowId) {
+      selectedMap.set(rowId, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MdmItemApi.Item) {
+  selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MdmItemApi.Item) {
+  const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+  await gridApi.grid.setCheckboxRow(row, !selected);
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑� */
+async function handleCellDblclick({ row }: { row: MdmItemApi.Item }) {
+  if (multiple.value) {
+    await toggleMultipleRow(row);
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夌墿鏂� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = getTableRows();
+  for (const row of rows) {
+    if (
+      row.id === undefined ||
+      row.id === null ||
+      !preSelectedIds.value.includes(row.id)
+    ) {
+      continue;
+    }
+    if (multiple.value) {
+      await gridApi.grid.setCheckboxRow(row, true);
+    } else {
+      await gridApi.grid.setRadioRow(row);
+      selectedRows.value = [row];
+      return;
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  formOptions: {
+    schema: useMdmItemSelectGridFormSchema(),
+  },
+  gridOptions: {
+    columns: useMdmItemSelectGridColumns(true),
+    height: 560,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      range: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getItemPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+    toolbarConfig: {
+      refresh: true,
+      search: true,
+    },
+  } as VxeTableGridOptions<MdmItemApi.Item>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: MdmItemApi.Item }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+  await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑鐗╂枡閫夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? true;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useMdmItemSelectGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴鐗╂枡閫夋嫨寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  await resetQueryState();
+}
+
+/** 纭閫夋嫨鐗╂枡 */
+function handleConfirm() {
+  const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+  if (rows.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', multiple.value ? rows : [rows[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="鐗╂枡閫夋嫨"
+    width="80%"
+    :destroy-on-close="true"
+    @ok="handleConfirm"
+    @cancel="closeModal"
+  >
+    <Grid table-title="鐗╂枡鍒楄〃" />
+  </Modal>
+</template>
diff --git a/src/views/basicData/mdm/components/select.vue b/src/views/basicData/mdm/components/select.vue
new file mode 100644
index 0000000..ad10d58
--- /dev/null
+++ b/src/views/basicData/mdm/components/select.vue
@@ -0,0 +1,138 @@
+<script lang="ts" setup>
+import type { MdmItemApi } from '#/api/mdm/item';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getItem } from '#/api/mdm/item';
+
+import MdmItemSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'MdmItemSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨鐗╂枡',
+  },
+);
+const emit = defineEmits<{
+  change: [item: MdmItemApi.Item | undefined];
+  'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs(); // 閫忎紶灞炴��
+const dialogRef = ref<InstanceType<typeof MdmItemSelectDialog>>(); // 鐗╂枡閫夋嫨寮圭獥
+const hovering = ref(false); // 鏄惁鎮仠
+const selectedItem = ref<MdmItemApi.Item>(); // 褰撳墠閫変腑鐗╂枡
+
+const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 閫夋嫨鍣ㄥ睍绀哄悕绉�
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+/** 鏍规嵁鐗╂枡缂栧彿鍥炴樉閫夋嫨鍣� */
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getItem(id);
+}
+
+watch(
+  () => props.modelValue,
+  (value) => {
+    resolveItemById(value);
+  },
+  { immediate: true },
+);
+
+/** 娓呯┖宸查�夌墿鏂� */
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+/** 鎵撳紑鐗╂枡閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 鍥炲~閫変腑鐨勭墿鏂� */
+function handleSelected(rows: MdmItemApi.Item[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>缂栫爜锛歿{ selectedItem.code || '-' }}</div>
+          <div>鍚嶇О锛歿{ selectedItem.name || '-' }}</div>
+          <div>瑙勬牸锛歿{ selectedItem.specification || '-' }}</div>
+          <div>鍗曚綅锛歿{ selectedItem.unitMeasureName || '-' }}</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <MdmItemSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/basicData/mdm/data.ts b/src/views/basicData/mdm/data.ts
index 479f150..910564a 100644
--- a/src/views/basicData/mdm/data.ts
+++ b/src/views/basicData/mdm/data.ts
@@ -6,7 +6,6 @@
 
 import { z } from '#/adapter/form';
 import { getUnitPage } from '#/api/mdm/unit';
-import { getWarehouseSimpleList } from '#/api/erp/stock/warehouse';
 
 /** 鐗╂枡绫诲瀷閫夐」 */
 export const ITEM_TYPE_OPTIONS = [
@@ -154,15 +153,11 @@
       },
     },
     {
-      component: 'ApiSelect',
+      component: 'Input',
       fieldName: 'warehouseId',
       label: '榛樿浠撳簱',
       componentProps: {
-        placeholder: '璇烽�夋嫨榛樿浠撳簱',
-        allowClear: true,
-        api: getWarehouseSimpleList,
-        labelField: 'name',
-        valueField: 'id',
+        placeholder: '璇疯緭鍏ラ粯璁や粨搴揑D',
       },
     },
     {
@@ -243,6 +238,7 @@
       component: 'Switch',
       fieldName: 'isBatchManaged',
       label: '鎵规绠$悊',
+      defaultValue: true,
       componentProps: {
         class: 'w-auto',
         checkedChildren: '鏄�',
diff --git a/src/views/basicData/mdm/modules/form.vue b/src/views/basicData/mdm/modules/form.vue
index 27b8097..8335105 100644
--- a/src/views/basicData/mdm/modules/form.vue
+++ b/src/views/basicData/mdm/modules/form.vue
@@ -1,7 +1,9 @@
 <script lang="ts" setup>
 import type { MdmItemApi } from '#/api/mdm/item';
+import type { MdmItemBatchConfigApi } from '#/api/mes/md/item-batch-config';
+import type { VbenFormApi } from '#/adapter/form';
 
-import { computed, ref } from 'vue';
+import { computed, ref, watch } from 'vue';
 
 import { useVbenModal } from '#/packages/effects/common-ui/src';
 
@@ -9,14 +11,22 @@
 
 import { useVbenForm } from '#/adapter/form';
 import { createItem, getItem, updateItem } from '#/api/mdm/item';
+import { saveItemBatchConfig } from '#/api/mes/md/item-batch-config';
 import { $t } from '#/locales';
 
 import { useFormSchema } from '../data';
+import ItemBatchConfig from '../components/batch-config.vue';
 
 defineOptions({ name: 'ItemForm' });
 
 const emit = defineEmits(['success']);
 const formData = ref<MdmItemApi.Item>();
+const batchConfigRef = ref<InstanceType<typeof ItemBatchConfig>>();
+const batchConfig = ref<Partial<MdmItemBatchConfigApi.ItemBatchConfig>>({});
+
+// 褰撳墠琛ㄥ崟涓殑鎵规绠$悊寮�鍏冲�硷紙鐢ㄤ簬鎺у埗鎵规閰嶇疆鏄剧ず锛�
+const currentIsBatchManaged = ref<boolean>(false);
+
 const getTitle = computed(() => {
   return formData.value?.id
     ? $t('ui.actionTitle.edit', ['鐗╂枡'])
@@ -34,6 +44,10 @@
   schema: useFormSchema(),
   showDefaultActions: false,
   wrapperClass: 'grid-cols-2',
+  // 鐩戝惉琛ㄥ崟鍊煎彉鍖�
+  handleValuesChange: (values: any) => {
+    currentIsBatchManaged.value = values?.isBatchManaged === true;
+  },
 });
 
 const [Modal, modalApi] = useVbenModal({
@@ -45,7 +59,18 @@
     modalApi.lock();
     const data = (await formApi.getValues()) as MdmItemApi.Item;
     try {
-      await (formData.value?.id ? updateItem(data) : createItem(data));
+      // 淇濆瓨鐗╂枡鍩烘湰淇℃伅
+      const itemId = await (formData.value?.id ? updateItem(data) : createItem(data));
+      const savedItemId = typeof itemId === 'number' ? itemId : data.id;
+
+      // 濡傛灉鍚敤鎵规绠$悊锛屼繚瀛樻壒娆¢厤缃�
+      if (data.isBatchManaged && savedItemId) {
+        await saveItemBatchConfig({
+          ...batchConfig.value,
+          itemId: savedItemId,
+        });
+      }
+
       await modalApi.close();
       emit('success');
       message.success($t('ui.actionMessage.operationSuccess'));
@@ -56,25 +81,50 @@
   async onOpenChange(isOpen: boolean) {
     if (!isOpen) {
       formData.value = undefined;
+      batchConfig.value = {};
+      currentIsBatchManaged.value = false;
       return;
     }
     const data = modalApi.getData<MdmItemApi.Item>();
-    if (!data || !data.id) {
-      return;
-    }
-    modalApi.lock();
-    try {
-      formData.value = await getItem(data.id);
-      await formApi.setValues(formData.value);
-    } finally {
-      modalApi.unlock();
+    if (data?.id) {
+      // 缂栬緫妯″紡
+      modalApi.lock();
+      try {
+        formData.value = await getItem(data.id);
+        await formApi.setValues(formData.value);
+        currentIsBatchManaged.value = formData.value?.isBatchManaged === true;
+        // 鍔犺浇鎵规閰嶇疆
+        if (batchConfigRef.value && currentIsBatchManaged.value) {
+          await batchConfigRef.value.loadConfig();
+        }
+      } finally {
+        modalApi.unlock();
+      }
+    } else {
+      // 鏂板妯″紡
+      formData.value = undefined;
+      await formApi.resetForm();
+      // 鑾峰彇琛ㄥ崟榛樿鍊硷紙isBatchManaged 榛樿 true锛�
+      const defaultValues = await formApi.getValues();
+      currentIsBatchManaged.value = defaultValues?.isBatchManaged === true;
     }
   },
 });
+
+/** 澶勭悊鎵规閰嶇疆鍙樺寲 */
+function handleBatchConfigChange(config: Partial<MdmItemBatchConfigApi.ItemBatchConfig>) {
+  batchConfig.value = config;
+}
 </script>
 
 <template>
   <Modal class="w-[1000px]" :title="getTitle">
     <Form class="mx-4" />
+    <ItemBatchConfig
+      ref="batchConfigRef"
+      :item-id="formData?.id"
+      :is-batch-managed="currentIsBatchManaged"
+      @change="handleBatchConfigChange"
+    />
   </Modal>
-</template>
\ No newline at end of file
+</template>
diff --git a/src/views/erp/sale/order/components/index.ts b/src/views/erp/sale/order/components/index.ts
new file mode 100644
index 0000000..1dbcbd2
--- /dev/null
+++ b/src/views/erp/sale/order/components/index.ts
@@ -0,0 +1,2 @@
+export { default as ErpSaleOrderSelect } from './select.vue';
+export { default as ErpSaleOrderSelectDialog } from './select-dialog.vue';
diff --git a/src/views/erp/sale/order/components/select-dialog.vue b/src/views/erp/sale/order/components/select-dialog.vue
new file mode 100644
index 0000000..9a1dfe1
--- /dev/null
+++ b/src/views/erp/sale/order/components/select-dialog.vue
@@ -0,0 +1,199 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getSaleOrderPage } from '#/api/erp/sale/order';
+
+defineOptions({ name: 'ErpSaleOrderSelectDialog' });
+
+const emit = defineEmits<{
+  selected: [rows: ErpSaleOrderApi.SaleOrder[]];
+}>();
+
+const open = ref(false);
+const multiple = ref(true);
+const status = ref<number | undefined>(undefined);
+const selectedRows = ref<ErpSaleOrderApi.SaleOrder[]>([]);
+const preSelectedIds = ref<number[]>([]);
+
+/** 鑾峰彇褰撳墠琛ㄦ牸鏁版嵁 */
+function getTableRows() {
+  return gridApi.grid.getTableData().fullData as ErpSaleOrderApi.SaleOrder[];
+}
+
+/** 鑾峰彇澶氶�夎褰� */
+function getMultipleSelectedRows() {
+  const selectedMap = new Map<number, ErpSaleOrderApi.SaleOrder>();
+  const records = [
+    ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+    ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+  ] as ErpSaleOrderApi.SaleOrder[];
+  records.forEach((row) => {
+    if (row.id) {
+      selectedMap.set(row.id, row);
+    }
+  });
+  return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+  selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: ErpSaleOrderApi.SaleOrder) {
+  selectedRows.value = [row];
+}
+
+/** 澶勭悊琛屽弻鍑� */
+async function handleCellDblclick({ row }: { row: ErpSaleOrderApi.SaleOrder }) {
+  if (multiple.value) {
+    const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+    await gridApi.grid.setCheckboxRow(row, !selected);
+    selectedRows.value = getMultipleSelectedRows();
+    return;
+  }
+  selectedRows.value = [row];
+  await gridApi.grid.setRadioRow(row);
+  handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夐攢鍞鍗� */
+async function applyPreSelection() {
+  if (preSelectedIds.value.length === 0) {
+    return;
+  }
+  const rows = getTableRows();
+  for (const row of rows) {
+    if (row.id && preSelectedIds.value.includes(row.id)) {
+      if (multiple.value) {
+        await gridApi.grid.setCheckboxRow(row, true);
+      } else {
+        await gridApi.grid.setRadioRow(row);
+        selectedRows.value = [row];
+        return;
+      }
+    }
+  }
+  if (multiple.value) {
+    selectedRows.value = getMultipleSelectedRows();
+  }
+}
+
+/** 鍒楄〃瀛楁 */
+function useGridColumns(isMultiple: boolean): VxeTableGridOptions<ErpSaleOrderApi.SaleOrder>['columns'] {
+  return [
+    { type: isMultiple ? 'checkbox' : 'radio', width: 50 },
+    { field: 'no', title: '璁㈠崟鍙�', minWidth: 140 },
+    { field: 'customerName', title: '瀹㈡埛', minWidth: 120 },
+    { field: 'orderTime', title: '璁㈠崟鏃堕棿', width: 120, formatter: 'formatDate' },
+    { field: 'totalCount', title: '鍚堣鏁伴噺', width: 100 },
+    { field: 'totalPrice', title: '鍚堣閲戦', width: 100 },
+  ];
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+  gridOptions: {
+    columns: useGridColumns(true),
+    height: 400,
+    keepSource: true,
+    checkboxConfig: {
+      highlight: true,
+      reserve: true,
+    },
+    radioConfig: {
+      highlight: true,
+      trigger: 'row',
+    },
+    proxyConfig: {
+      ajax: {
+        query: async ({ page }, formValues) => {
+          return await getSaleOrderPage({
+            pageNo: page.currentPage,
+            pageSize: page.pageSize,
+            status: status.value,
+            ...formValues,
+          });
+        },
+      },
+    },
+    rowConfig: {
+      keyField: 'id',
+      isHover: true,
+    },
+  } as VxeTableGridOptions<ErpSaleOrderApi.SaleOrder>,
+  gridEvents: {
+    cellDblclick: handleCellDblclick,
+    checkboxAll: handleCheckboxSelectChange,
+    checkboxChange: handleCheckboxSelectChange,
+    radioChange: ({ row }: { row: ErpSaleOrderApi.SaleOrder }) => {
+      handleRadioChange(row);
+    },
+  },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+  selectedRows.value = [];
+  await gridApi.grid.clearCheckboxRow();
+  await gridApi.grid.clearCheckboxReserve();
+  await gridApi.grid.clearRadioRow();
+}
+
+/** 鎵撳紑閿�鍞鍗曢�夋嫨寮圭獥 */
+async function openModal(
+  selectedIds?: number[],
+  options?: { multiple?: boolean; status?: number },
+) {
+  open.value = true;
+  multiple.value = options?.multiple ?? true;
+  status.value = options?.status;
+  preSelectedIds.value = selectedIds || [];
+  await nextTick();
+  gridApi.setGridOptions({
+    columns: useGridColumns(multiple.value),
+  });
+  await resetQueryState();
+  await gridApi.query();
+  await nextTick();
+  await applyPreSelection();
+}
+
+/** 鍏抽棴閿�鍞鍗曢�夋嫨寮圭獥 */
+async function closeModal() {
+  open.value = false;
+  await resetQueryState();
+}
+
+/** 纭閫夋嫨閿�鍞鍗� */
+function handleConfirm() {
+  const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+  if (rows.length === 0) {
+    message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+    return;
+  }
+  emit('selected', multiple.value ? rows : [rows[0]!]);
+  open.value = false;
+}
+
+defineExpose({ open: openModal });
+</script>
+
+<template>
+  <Modal
+    v-model:open="open"
+    title="閿�鍞鍗曢�夋嫨"
+    width="70%"
+    :destroy-on-close="true"
+    @ok="handleConfirm"
+    @cancel="closeModal"
+  >
+    <Grid table-title="閿�鍞鍗曞垪琛�" />
+  </Modal>
+</template>
diff --git a/src/views/erp/sale/order/components/select.vue b/src/views/erp/sale/order/components/select.vue
new file mode 100644
index 0000000..12652d0
--- /dev/null
+++ b/src/views/erp/sale/order/components/select.vue
@@ -0,0 +1,135 @@
+<script lang="ts" setup>
+import type { ErpSaleOrderApi } from '#/api/erp/sale/order';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getSaleOrder } from '#/api/erp/sale/order';
+
+import ErpSaleOrderSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'ErpSaleOrderSelect', inheritAttrs: false });
+
+const props = withDefaults(
+  defineProps<{
+    allowClear?: boolean;
+    disabled?: boolean;
+    modelValue?: number;
+    placeholder?: string;
+    status?: number;
+  }>(),
+  {
+    allowClear: true,
+    disabled: false,
+    modelValue: undefined,
+    placeholder: '璇烽�夋嫨閿�鍞鍗�',
+    status: undefined,
+  },
+);
+
+const emit = defineEmits(['change', 'update:modelValue']);
+
+const attrs = useAttrs();
+const dialogRef = ref<InstanceType<typeof ErpSaleOrderSelectDialog>>();
+const hovering = ref(false);
+const selectedItem = ref<ErpSaleOrderApi.SaleOrder>();
+
+const displayLabel = computed(() => selectedItem.value?.no ?? '');
+const showClear = computed(
+  () =>
+    props.allowClear &&
+    !props.disabled &&
+    hovering.value &&
+    props.modelValue !== null,
+);
+
+async function resolveItemById(id: number | undefined) {
+  if (!id) {
+    selectedItem.value = undefined;
+    return;
+  }
+  if (selectedItem.value?.id === id) {
+    return;
+  }
+  selectedItem.value = await getSaleOrder(id);
+}
+
+watch(
+  () => props.modelValue,
+  (value) => {
+    resolveItemById(value);
+  },
+  { immediate: true },
+);
+
+function clearSelected() {
+  selectedItem.value = undefined;
+  emit('update:modelValue', undefined);
+  emit('change', undefined);
+}
+
+function handleClick(event: MouseEvent) {
+  if (props.disabled) {
+    return;
+  }
+  const target = event.target as HTMLElement;
+  if (showClear.value && target.closest('.ant-input-suffix')) {
+    event.stopPropagation();
+    clearSelected();
+    return;
+  }
+  const selectedIds = (
+    props.modelValue === null ? [] : [props.modelValue]
+  ) as number[];
+  dialogRef.value?.open(selectedIds, { multiple: false, status: props.status });
+}
+
+function handleSelected(rows: ErpSaleOrderApi.SaleOrder[]) {
+  const item = rows[0];
+  if (!item) {
+    return;
+  }
+  selectedItem.value = item;
+  emit('update:modelValue', item.id);
+  emit('change', item);
+}
+</script>
+
+<template>
+  <div
+    v-bind="attrs"
+    class="w-full"
+    :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+    @click="handleClick"
+    @mouseenter="hovering = true"
+    @mouseleave="hovering = false"
+  >
+    <Tooltip :mouse-enter-delay="0.5" :open="selectedItem ? undefined : false">
+      <template #title>
+        <div v-if="selectedItem" class="leading-6">
+          <div>璁㈠崟鍙凤細{{ selectedItem.no || '-' }}</div>
+          <div>瀹㈡埛锛歿{ selectedItem.customerName || '-' }}</div>
+          <div>璁㈠崟鏃堕棿锛歿{ selectedItem.orderTime || '-' }}</div>
+          <div>鍚堣閲戦锛歿{ selectedItem.totalPrice || '-' }} 鍏�</div>
+        </div>
+      </template>
+      <Input
+        :disabled="disabled"
+        :placeholder="placeholder"
+        :value="displayLabel"
+        readonly
+      >
+        <template #suffix>
+          <IconifyIcon
+            class="size-4"
+            :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+          />
+        </template>
+      </Input>
+    </Tooltip>
+  </div>
+  <ErpSaleOrderSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/erp/sale/order/data.ts b/src/views/erp/sale/order/data.ts
index 9363c82..4428be2 100644
--- a/src/views/erp/sale/order/data.ts
+++ b/src/views/erp/sale/order/data.ts
@@ -270,7 +270,7 @@
     },
     {
       title: '鎿嶄綔',
-      width: 50,
+      width: 320,
       fixed: 'right',
       slots: { default: 'actions' },
       visible: !disabled,
diff --git a/src/views/erp/sale/order/index.vue b/src/views/erp/sale/order/index.vue
index c6bd0cd..99cf346 100644
--- a/src/views/erp/sale/order/index.vue
+++ b/src/views/erp/sale/order/index.vue
@@ -19,6 +19,7 @@
   getSaleOrderPage,
   updateSaleOrderStatus,
 } from '#/api/erp/sale/order';
+import { generateFromSaleOrder } from '#/api/mes/wm/salesnotice';
 import { $t } from '#/locales';
 
 import { useGridColumns, useGridFormSchema } from './data';
@@ -136,9 +137,23 @@
 function handleViewContract(row: ErpSaleOrderApi.SaleOrder) {
   if (row.contractId) {
     router.push({
-      path: '/crm/contract/detail',
-      query: { id: row.contractId },
+      path: `/crm/contract/detail/${row.contractId}`,
     });
+  }
+}
+
+/** 鐢熸垚鍙戣揣閫氱煡鍗� */
+async function handleGenerateNotice(row: ErpSaleOrderApi.SaleOrder) {
+  try {
+    const noticeId = await generateFromSaleOrder(row.id!);
+    message.success('鐢熸垚鍙戣揣閫氱煡鍗曟垚鍔�');
+    // 璺宠浆鍒板彂璐ч�氱煡鍗曢〉闈�
+    router.push({
+      path: '/wls/sales-notice',
+      query: { id: noticeId },
+    });
+  } catch (e) {
+    console.error(e);
   }
 }
 
@@ -296,6 +311,14 @@
               },
             },
             {
+              label: '鐢熸垚鍙戣揣閫氱煡鍗�',
+              type: 'link',
+              icon: 'ant-design:file-protect-outlined',
+              auth: ['mes:wm-sales-notice:create'],
+              ifShow: () => row.status === 20,
+              onClick: handleGenerateNotice.bind(null, row),
+            },
+            {
               label: $t('common.delete'),
               type: 'link',
               danger: true,
diff --git a/src/views/wls/materialstock/components/batch-config-display.vue b/src/views/wls/materialstock/components/batch-config-display.vue
new file mode 100644
index 0000000..ff4c52b
--- /dev/null
+++ b/src/views/wls/materialstock/components/batch-config-display.vue
@@ -0,0 +1,96 @@
+<script lang="ts" setup>
+import type { MdmItemBatchConfigApi } from '#/api/mes/md/item-batch-config';
+
+import { computed, ref, watch } from 'vue';
+
+import { Divider, Tag } from 'ant-design-vue';
+
+import { getItemBatchConfigByItemId } from '#/api/mes/md/item-batch-config';
+
+const props = defineProps<{
+  itemId?: number;
+  isBatchManaged?: boolean;
+}>();
+
+// 鎵规閰嶇疆鏁版嵁
+const config = ref<MdmItemBatchConfigApi.ItemBatchConfig | null>(null);
+
+// 鏄惁鍔犺浇涓�
+const loading = ref(false);
+
+// 鏄惁鏄剧ず锛堝惎鐢ㄦ壒娆$鐞嗘椂鏄剧ず锛�
+const visible = computed(() => props.isBatchManaged === true);
+
+// 宸插惎鐢ㄧ殑鎵规灞炴�у垪琛紙浠呬繚鐣�7涓瓧娈碉級
+const enabledProps = computed(() => {
+  if (!config.value) return [];
+  const props: { label: string; value: boolean }[] = [];
+  if (config.value.produceDateFlag) props.push({ label: '鐢熶骇鏃ユ湡', value: true });
+  if (config.value.expireDateFlag) props.push({ label: '鏈夋晥鏈�', value: true });
+  if (config.value.receiptDateFlag) props.push({ label: '鍏ュ簱鏃ユ湡', value: true });
+  if (config.value.vendorFlag) props.push({ label: '渚涘簲鍟�', value: true });
+  if (config.value.purchaseOrderCodeFlag) props.push({ label: '閲囪喘璁㈠崟鍙�', value: true });
+  if (config.value.lotNumberFlag) props.push({ label: '鐢熶骇鎵瑰彿', value: true });
+  if (config.value.qualityStatusFlag) props.push({ label: '璐ㄩ噺鐘舵��', value: true });
+  return props;
+});
+
+/** 鍔犺浇鎵规閰嶇疆 */
+async function loadConfig() {
+  if (!props.itemId || !props.isBatchManaged) {
+    config.value = null;
+    return;
+  }
+  loading.value = true;
+  try {
+    const data = await getItemBatchConfigByItemId(props.itemId);
+    config.value = data;
+  } catch {
+    config.value = null;
+  } finally {
+    loading.value = false;
+  }
+}
+
+/** 閲嶇疆 */
+function reset() {
+  config.value = null;
+  loading.value = false;
+}
+
+// 鐩戝惉 itemId 鍙樺寲锛屽姞杞介厤缃�
+watch(
+  () => [props.itemId, props.isBatchManaged],
+  () => {
+    if (props.itemId && props.isBatchManaged) {
+      loadConfig();
+    } else {
+      reset();
+    }
+  },
+  { immediate: true },
+);
+
+defineExpose({
+  loadConfig,
+  reset,
+});
+</script>
+
+<template>
+  <div v-if="visible" class="mt-4 rounded border border-gray-200 bg-gray-50 p-4">
+    <Divider orientation="left">鎵规灞炴��</Divider>
+    <div v-if="loading" class="text-center text-gray-500">鍔犺浇涓�...</div>
+    <div v-else-if="enabledProps.length === 0" class="text-center text-gray-500">
+      璇ョ墿鏂欐湭閰嶇疆鎵规灞炴��
+    </div>
+    <div v-else class="flex flex-wrap gap-2">
+      <Tag v-for="prop in enabledProps" :key="prop.label" color="blue">
+        {{ prop.label }}
+      </Tag>
+    </div>
+    <p class="mt-2 text-xs text-gray-500">
+      浠ヤ笂瀛楁鍒涘缓鎵规鏃跺繀濉�
+    </p>
+  </div>
+</template>
diff --git a/src/views/wls/materialstock/components/index.ts b/src/views/wls/materialstock/components/index.ts
index aa23039..2cb4faf 100644
--- a/src/views/wls/materialstock/components/index.ts
+++ b/src/views/wls/materialstock/components/index.ts
@@ -1,2 +1,3 @@
 export { default as WmMaterialStockSelectDialog } from './select-dialog.vue';
 export { default as WmMaterialStockSelect } from './select.vue';
+export { default as WmStockAdjustModal } from './stock-adjust-modal.vue';
diff --git a/src/views/wls/materialstock/components/stock-adjust-modal.vue b/src/views/wls/materialstock/components/stock-adjust-modal.vue
new file mode 100644
index 0000000..31f31e9
--- /dev/null
+++ b/src/views/wls/materialstock/components/stock-adjust-modal.vue
@@ -0,0 +1,488 @@
+<script lang="ts" setup>
+import type {
+  MaterialStockAdjustInParams,
+  MaterialStockAdjustOutParams,
+} from '#/api/mes/wm/materialstock';
+import type { MdmItemApi } from '#/api/mdm/item';
+import type { MdmItemBatchConfigApi } from '#/api/mes/md/item-batch-config';
+import type { VbenFormSchema } from '#/adapter/form';
+
+import { ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+import { z } from '#/adapter/form';
+import { useVbenForm } from '#/adapter/form';
+import { message } from 'ant-design-vue';
+import { markRaw } from 'vue';
+
+import {
+  adjustMaterialStockIn,
+  adjustMaterialStockOut,
+} from '#/api/mes/wm/materialstock';
+import { getItem } from '#/api/mdm/item';
+import { getItemBatchConfigByItemId } from '#/api/mes/md/item-batch-config';
+import { MdmItemSelect } from '#/views/basicData/mdm/components';
+import { MdVendorSelect } from '#/views/mes/md/vendor/components';
+import BatchConfigDisplay from './batch-config-display.vue';
+import {
+  WmWarehouseAreaSelect,
+  WmWarehouseLocationSelect,
+  WmWarehouseSelect,
+} from '#/views/wls/warehouse/components';
+
+const emit = defineEmits<{
+  success: [];
+}>();
+
+// 鎿嶄綔绫诲瀷锛�1=鍏ュ簱锛�2=鍑哄簱
+const operateType = ref<1 | 2>(1);
+const selectedItem = ref<MdmItemApi.Item | null>(null);
+const selectedWarehouse = ref<any>(null);
+const selectedLocation = ref<any>(null);
+
+// 鏄惁鍚敤鎵规绠$悊
+const batchEnabled = ref(false);
+// 鏄惁鎵嬪姩濉啓鎵规鍙�
+const manualBatchEntry = ref(false);
+// 鐗╂枡鎵规閰嶇疆
+const batchConfig = ref<MdmItemBatchConfigApi.ItemBatchConfig | null>(null);
+// 鎵规閰嶇疆灞曠ず缁勪欢寮曠敤
+const batchConfigDisplayRef = ref<InstanceType<typeof BatchConfigDisplay>>();
+
+// 鏍囬
+const modalTitle = ref('搴撳瓨璋冩暣');
+
+/** 鍔犺浇鐗╂枡鎵规閰嶇疆骞舵洿鏂板瓧娈垫樉绀� */
+async function loadBatchConfigAndUpdateFields(itemId: number) {
+  try {
+    const config = await getItemBatchConfigByItemId(itemId);
+    batchConfig.value = config;
+  } catch {
+    batchConfig.value = null;
+  }
+  await updateBatchFields();
+}
+
+/** 鏇存柊鎵规瀛楁鏄剧ず锛堟牴鎹厤缃睍绀哄凡鍚敤鐨勫睘鎬э級 */
+async function updateBatchFields() {
+  const enabled = selectedItem.value?.isBatchManaged === true;
+  batchEnabled.value = enabled;
+
+  if (!enabled) {
+    // 闅愯棌鎵�鏈夋壒娆$浉鍏冲瓧娈�
+    formApi.updateSchema([
+      { fieldName: 'batchCode', dependencies: { show: () => false } },
+      { fieldName: 'produceDate', dependencies: { show: () => false } },
+      { fieldName: 'expireDate', dependencies: { show: () => false } },
+      { fieldName: 'manualBatchEntry', dependencies: { show: () => false } },
+      { fieldName: 'vendorId', dependencies: { show: () => true } },
+    ]);
+    return;
+  }
+
+  // 鍚敤鎵规绠$悊锛屾牴鎹厤缃樉绀哄瓧娈�
+  const config = batchConfig.value;
+  const isIn = operateType.value === 1;
+
+  // 鏄惁鎵嬪姩濉啓鎵规鍙锋帶鍒舵壒娆″彿瀛楁鐨勬樉绀�
+  const showBatchCode = manualBatchEntry.value;
+  const showProduceDate = isIn && !!config?.produceDateFlag;
+  const showExpireDate = isIn && !!config?.expireDateFlag;
+
+  formApi.updateSchema([
+    { fieldName: 'batchCode', dependencies: { show: () => showBatchCode } },
+    { fieldName: 'produceDate', dependencies: { show: () => showProduceDate } },
+    { fieldName: 'expireDate', dependencies: { show: () => showExpireDate } },
+    { fieldName: 'manualBatchEntry', dependencies: { show: () => true } },
+  ]);
+}
+
+/** 鐗╂枡閫夋嫨鍙樺寲 */
+async function handleItemChange(item: MdmItemApi.Item | undefined) {
+  selectedItem.value = item || null;
+  if (item?.id) {
+    const itemDetail = await getItem(item.id);
+    selectedItem.value = itemDetail;
+    // 鍔犺浇鐗╂枡鎵规閰嶇疆骞舵洿鏂板瓧娈�
+    await loadBatchConfigAndUpdateFields(item.id);
+  } else {
+    batchConfig.value = null;
+    batchEnabled.value = false;
+  }
+  // 娓呯┖鎵规鐩稿叧鍊�
+  await formApi.setFieldValue('batchCode', undefined);
+  await formApi.setFieldValue('produceDate', undefined);
+  await formApi.setFieldValue('expireDate', undefined);
+  await formApi.setFieldValue('manualBatchEntry', false);
+}
+
+/** 浠撳簱閫夋嫨鍙樺寲 */
+async function handleWarehouseChange(item: any) {
+  selectedWarehouse.value = item;
+  await formApi.setFieldValue('locationId', undefined);
+  await formApi.setFieldValue('areaId', undefined);
+}
+
+/** 搴撳尯閫夋嫨鍙樺寲 */
+async function handleLocationChange(item: any) {
+  selectedLocation.value = item;
+  await formApi.setFieldValue('areaId', undefined);
+}
+
+/** 鎵嬪姩濉啓鎵规鍙峰彉鍖� */
+async function handleManualBatchEntryChange(value: boolean) {
+  manualBatchEntry.value = value;
+  await updateBatchFields();
+  if (!value) {
+    await formApi.setFieldValue('batchCode', undefined);
+  }
+}
+/** 鎿嶄綔绫诲瀷鍙樺寲 */
+async function handleOperateTypeChange(type: 1 | 2) {
+  operateType.value = type;
+  modalTitle.value = type === 1 ? '搴撳瓨鍏ュ簱' : '搴撳瓨鍑哄簱';
+  await formApi.setFieldValue('operateType', type);
+  // 鏇存柊鎵规瀛楁鏄剧ず
+  await updateBatchFields();
+}
+
+/** 鑾峰彇琛ㄥ崟 schema */
+function getSchema(): VbenFormSchema[] {
+  return [
+    {
+      fieldName: 'operateType',
+      label: '鎿嶄綔绫诲瀷',
+      component: 'RadioGroup',
+      defaultValue: 1,
+      componentProps: {
+        options: [
+          { label: '鍏ュ簱', value: 1 },
+          { label: '鍑哄簱', value: 2 },
+        ],
+        onChange: handleOperateTypeChange,
+      },
+      rules: 'required',
+    },
+    {
+      fieldName: 'itemId',
+      label: '鐗╂枡',
+      component: markRaw(MdmItemSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐗╂枡',
+        onChange: handleItemChange,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'warehouseId',
+      label: '浠撳簱',
+      component: markRaw(WmWarehouseSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨浠撳簱',
+        onChange: handleWarehouseChange,
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'locationId',
+      label: '搴撳尯',
+      component: markRaw(WmWarehouseLocationSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撳尯',
+        warehouseId: selectedWarehouse.value?.id,
+        onChange: handleLocationChange,
+      },
+      dependencies: {
+        triggerFields: ['warehouseId'],
+        componentProps: (values: any) => ({
+          placeholder: '璇烽�夋嫨搴撳尯',
+          warehouseId: values.warehouseId,
+          onChange: handleLocationChange,
+        }),
+        trigger: (values: any, formApi: any) => {
+          if (values.locationId !== undefined) {
+            void formApi.setFieldValue('locationId', undefined);
+            void formApi.setFieldValue('areaId', undefined);
+          }
+        },
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'areaId',
+      label: '搴撲綅',
+      component: markRaw(WmWarehouseAreaSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨搴撲綅',
+        locationId: selectedLocation.value?.id,
+      },
+      dependencies: {
+        triggerFields: ['locationId'],
+        componentProps: (values: any) => ({
+          placeholder: '璇烽�夋嫨搴撲綅',
+          locationId: values.locationId,
+        }),
+        trigger: (values: any, formApi: any) => {
+          if (values.areaId !== undefined) {
+            void formApi.setFieldValue('areaId', undefined);
+          }
+        },
+      },
+      rules: 'selectRequired',
+    },
+    // 鏄惁鎵嬪姩濉啓鎵规鍙凤紙鍚敤鎵规绠$悊鏃舵樉绀猴級
+    {
+      fieldName: 'manualBatchEntry',
+      label: '鎵嬪姩濉啓鎵规鍙�',
+      component: 'Switch',
+      defaultValue: false,
+      componentProps: {
+        class: 'w-auto',
+        checkedChildren: '鏄�',
+        unCheckedChildren: '鍚�',
+        onChange: handleManualBatchEntryChange,
+      },
+      dependencies: {
+        triggerFields: ['itemId'],
+        show: () => false, // 榛樿闅愯棌锛屽惎鐢ㄦ壒娆$鐞嗘椂鏄剧ず
+      },
+    },
+    // 鎵规鍙凤紙鎵嬪姩濉啓鎵规鍙锋椂鏄剧ず锛�
+    {
+      fieldName: 'batchCode',
+      label: '鎵规鍙�',
+      component: 'Input',
+      componentProps: {
+        placeholder: '璇疯緭鍏ユ壒娆″彿',
+      },
+      dependencies: {
+        triggerFields: ['itemId', 'manualBatchEntry'],
+        show: () => false, // 榛樿闅愯棌锛屾牴鎹墜鍔ㄥ~鍐欒缃樉绀�
+      },
+      // 鏍规嵁閰嶇疆鍐冲畾鏄惁蹇呭~
+      rules: z.string().optional(),
+    },
+    // 鐢熶骇鏃ユ湡锛堟牴鎹壒娆¢厤缃樉绀猴紝浠呭叆搴擄級
+    {
+      fieldName: 'produceDate',
+      label: '鐢熶骇鏃ユ湡',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鐢熶骇鏃ユ湡',
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+      },
+      dependencies: {
+        triggerFields: ['itemId'],
+        show: () => false, // 榛樿闅愯棌
+      },
+      rules: 'required',
+    },
+    // 鏈夋晥鏈燂紙鏍规嵁鎵规閰嶇疆鏄剧ず锛屼粎鍏ュ簱锛�
+    {
+      fieldName: 'expireDate',
+      label: '鏈夋晥鏈熻嚦',
+      component: 'DatePicker',
+      componentProps: {
+        placeholder: '璇烽�夋嫨鏈夋晥鏈�',
+        format: 'YYYY-MM-DD',
+        valueFormat: 'YYYY-MM-DD',
+      },
+      dependencies: {
+        triggerFields: ['itemId'],
+        show: () => false, // 榛樿闅愯棌
+      },
+    },
+    {
+      fieldName: 'vendorId',
+      label: '渚涘簲鍟�',
+      component: markRaw(MdVendorSelect),
+      componentProps: {
+        placeholder: '璇烽�夋嫨渚涘簲鍟�',
+      },
+      rules: 'selectRequired',
+    },
+    {
+      fieldName: 'quantity',
+      label: '鏁伴噺',
+      component: 'InputNumber',
+      componentProps: {
+        class: '!w-full',
+        placeholder: '璇疯緭鍏ユ暟閲�',
+        precision: 2,
+        min: 0.01,
+      },
+      rules: z.number().positive('鏁伴噺蹇呴』澶т簬0'),
+    },
+    {
+      fieldName: 'reason',
+      label: '鍘熷洜',
+      component: 'Textarea',
+      formItemClass: 'col-span-2',
+      componentProps: {
+        placeholder: '璇疯緭鍏ュ師鍥�',
+        rows: 3,
+      },
+    },
+  ];
+}
+
+const [Form, formApi] = useVbenForm({
+  commonConfig: {
+    componentProps: {
+      class: 'w-full',
+    },
+    formItemClass: 'col-span-1',
+    labelWidth: 100,
+  },
+  wrapperClass: 'grid-cols-2',
+  layout: 'horizontal',
+  schema: getSchema(),
+  showDefaultActions: false,
+});
+
+const [Modal, modalApi] = useVbenModal({
+  fullscreen: false,
+  onCancel: () => {
+    modalApi.close();
+  },
+  onConfirm: async () => {
+    await handleSubmit();
+  },
+  onOpenChange: async (isOpen: boolean) => {
+    if (!isOpen) {
+      selectedItem.value = null;
+      selectedWarehouse.value = null;
+      selectedLocation.value = null;
+      batchEnabled.value = false;
+      batchConfig.value = null;
+      operateType.value = 1;
+      modalTitle.value = '搴撳瓨璋冩暣';
+      return;
+    }
+    // 閲嶇疆琛ㄥ崟
+    await formApi.resetForm();
+    // 榛樿闅愯棌鎵规鐩稿叧瀛楁
+    formApi.updateSchema([
+      { fieldName: 'batchCode', dependencies: { show: () => false } },
+      { fieldName: 'produceDate', dependencies: { show: () => false } },
+      { fieldName: 'expireDate', dependencies: { show: () => false } },
+      { fieldName: 'manualBatchEntry', dependencies: { show: () => false } },
+    ]);
+    // 閲嶇疆鎵嬪姩濉啓鎵规鍙风姸鎬�
+    manualBatchEntry.value = false;
+    // 濡傛灉鏈変紶鍏ョ墿鏂欎俊鎭紝鍥炲~琛ㄥ崟
+    const data = modalApi.getData();
+    if (data?.itemId) {
+      await formApi.setValues({
+        itemId: data.itemId,
+        warehouseId: data.warehouseId,
+        locationId: data.locationId,
+        areaId: data.areaId,
+      });
+      if (data.itemId) {
+        const itemDetail = await getItem(data.itemId);
+        selectedItem.value = itemDetail;
+        await loadBatchConfigAndUpdateFields(data.itemId);
+      }
+    }
+  },
+  title: modalTitle,
+});
+
+/** 鎻愪氦琛ㄥ崟 */
+async function handleSubmit() {
+  const { valid } = await formApi.validate();
+  if (!valid) {
+    return;
+  }
+
+  const values = await formApi.getValues();
+
+  const {
+    itemId,
+    warehouseId,
+    locationId,
+    areaId,
+    batchCode,
+    produceDate,
+    expireDate,
+    vendorId,
+    quantity,
+    reason,
+  } = values;
+
+  // 鍚敤鎵规绠$悊鏃剁殑鏍¢獙
+  if (batchEnabled.value) {
+    // 鎵嬪姩濉啓鎵规鍙锋椂锛屾壒娆″彿蹇呭~
+    if (manualBatchEntry.value && !batchCode) {
+      message.error('璇疯緭鍏ユ壒娆″彿');
+      return;
+    }
+    // 鏍规嵁鎵规閰嶇疆鏍¢獙蹇呭~瀛楁锛堜粎鍏ュ簱锛�
+    if (operateType.value === 1) {
+      const config = batchConfig.value;
+      if (config?.produceDateFlag && !produceDate) {
+        message.error('璇烽�夋嫨鐢熶骇鏃ユ湡');
+        return;
+      }
+      if (config?.expireDateFlag && !expireDate) {
+        message.error('璇烽�夋嫨鏈夋晥鏈�');
+        return;
+      }
+    }
+  }
+
+  try {
+    const type = operateType.value;
+
+    if (type === 1) {
+      // 鍏ュ簱
+      const params: MaterialStockAdjustInParams = {
+        itemId,
+        warehouseId,
+        locationId,
+        areaId,
+        batchCode,
+        produceDate,
+        expireDate,
+        vendorId,
+        quantity,
+        reason,
+      };
+      await adjustMaterialStockIn(params);
+      message.success('鍏ュ簱鎴愬姛');
+    } else {
+      // 鍑哄簱
+      const params: MaterialStockAdjustOutParams = {
+        itemId,
+        warehouseId,
+        locationId,
+        areaId,
+        batchCode,
+        vendorId,
+        quantity,
+        reason,
+      };
+      await adjustMaterialStockOut(params);
+      message.success('鍑哄簱鎴愬姛');
+    }
+
+    emit('success');
+    modalApi.close();
+  } catch (error) {
+    // 閿欒鐢辫姹傛嫤鎴櫒澶勭悊
+  }
+}
+</script>
+
+<template>
+  <Modal class="w-3/5">
+    <Form class="mx-4" />
+    <BatchConfigDisplay
+      ref="batchConfigDisplayRef"
+      :item-id="selectedItem?.id"
+      :is-batch-managed="batchEnabled"
+    />
+  </Modal>
+</template>
diff --git a/src/views/wls/materialstock/data.ts b/src/views/wls/materialstock/data.ts
index c6f0ceb..4befafd 100644
--- a/src/views/wls/materialstock/data.ts
+++ b/src/views/wls/materialstock/data.ts
@@ -108,6 +108,41 @@
       width: 100,
     },
     {
+      field: 'frozenQuantity',
+      title: '鍐荤粨鏁伴噺',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'reservedQuantity',
+      title: '鍗犵敤閲�',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'inTransitQuantity',
+      title: '鍦ㄩ�旈噺',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'availableQuantity',
+      title: '鍙敤閲�',
+      width: 100,
+      cellRender: {
+        name: 'CellText',
+        props: {
+          class: ({ row }: { row: MesWmMaterialStockApi.MaterialStock }) =>
+            (row.availableQuantity || 0) < 0 ? 'text-red-500 font-bold' : '',
+          formatter: ({ row }: { row: MesWmMaterialStockApi.MaterialStock }) =>
+            ((row.availableQuantity || 0) as number).toFixed(2),
+        },
+      },
+    },
+    {
       field: 'unitMeasureName',
       title: '鍗曚綅',
       width: 80,
@@ -288,6 +323,41 @@
       width: 100,
     },
     {
+      field: 'frozenQuantity',
+      title: '鍐荤粨鏁伴噺',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'reservedQuantity',
+      title: '鍗犵敤閲�',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'inTransitQuantity',
+      title: '鍦ㄩ�旈噺',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) =>
+        (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'availableQuantity',
+      title: '鍙敤閲�',
+      width: 100,
+      cellRender: {
+        name: 'CellText',
+        props: {
+          class: ({ row }: { row: MesWmMaterialStockApi.MaterialStock }) =>
+            (row.availableQuantity || 0) < 0 ? 'text-red-500 font-bold' : '',
+          formatter: ({ row }: { row: MesWmMaterialStockApi.MaterialStock }) =>
+            ((row.availableQuantity || 0) as number).toFixed(2),
+        },
+      },
+    },
+    {
       field: 'receiptTime',
       title: '鍏ュ簱鏃ユ湡',
       width: 180,
diff --git a/src/views/wls/materialstock/index.vue b/src/views/wls/materialstock/index.vue
index a9353f0..3ae7731 100644
--- a/src/views/wls/materialstock/index.vue
+++ b/src/views/wls/materialstock/index.vue
@@ -19,11 +19,17 @@
 import { MdItemTypeTree } from '#/views/mes/md/item/type/components';
 import { WmBatchDetail } from '#/views/wls/batch/components';
 import AreaForm from '#/views/wls/warehouse/area/modules/form.vue';
+import { WmStockAdjustModal } from './components';
 
 import { useGridColumns, useGridFormSchema } from './data';
 
 const [AreaModal, areaModalApi] = useVbenModal({
   connectedComponent: AreaForm,
+  destroyOnClose: true,
+});
+
+const [StockAdjustModal, stockAdjustModalApi] = useVbenModal({
+  connectedComponent: WmStockAdjustModal,
   destroyOnClose: true,
 });
 
@@ -56,6 +62,16 @@
     return;
   }
   areaModalApi.setData({ formType: 'detail', id: row.areaId }).open();
+}
+
+/** 鎵撳紑搴撳瓨璋冩暣寮圭獥 */
+function handleOpenStockAdjust() {
+  stockAdjustModalApi.setData({}).open();
+}
+
+/** 搴撳瓨璋冩暣鎴愬姛鍥炶皟 */
+function handleStockAdjustSuccess() {
+  gridApi.query();
 }
 
 /** 鎵撳紑鎵规璇︽儏寮圭獥 */
@@ -122,6 +138,7 @@
 <template>
   <Page auto-content-height><AreaModal />
     <WmBatchDetail ref="batchDetailRef" />
+    <StockAdjustModal @success="handleStockAdjustSuccess" />
 
     <div class="flex h-full w-full">
       <!-- 宸︿晶鐗╂枡鍒嗙被鏍� -->
@@ -135,6 +152,13 @@
             <TableAction
               :actions="[
                 {
+                  label: '搴撳瓨璋冩暣',
+                  type: 'default',
+                  icon: ACTION_ICON.EDIT,
+                  auth: ['mes:wm-material-stock:adjust'],
+                  onClick: handleOpenStockAdjust,
+                },
+                {
                   label: $t('ui.actionTitle.export'),
                   type: 'primary',
                   icon: ACTION_ICON.DOWNLOAD,
diff --git a/src/views/wls/salesnotice/data.ts b/src/views/wls/salesnotice/data.ts
index f9a1931..fcadf3c 100644
--- a/src/views/wls/salesnotice/data.ts
+++ b/src/views/wls/salesnotice/data.ts
@@ -5,12 +5,17 @@
 
 import { h, markRaw } from 'vue';
 
-import { DICT_TYPE, MesAutoCodeRuleCode } from '@vben/constants';
+import {
+  DICT_TYPE,
+  MesAutoCodeRuleCode,
+  MesWmSalesNoticeOutStatusEnum,
+} from '@vben/constants';
 
-import { Button } from 'ant-design-vue';
+import { Button, Tag } from 'ant-design-vue';
 
 import { z } from '#/adapter/form';
 import { generateAutoCode } from '#/api/mes/md/autocode/record';
+import { ErpSaleOrderSelect } from '#/views/erp/sale/order/components';
 import { MdClientSelect } from '#/views/mes/md/client/components';
 import { MdItemSelect } from '#/views/mes/md/item/components';
 
@@ -79,11 +84,27 @@
       rules: 'required',
     },
     {
-      fieldName: 'salesOrderCode',
-      label: '閿�鍞鍗曠紪鍙�',
-      component: 'Input',
+      fieldName: 'saleOrderId',
+      label: '閿�鍞鍗�',
+      component: markRaw(ErpSaleOrderSelect),
       componentProps: {
-        placeholder: '璇疯緭鍏ラ攢鍞鍗曠紪鍙�',
+        placeholder: '璇烽�夋嫨閿�鍞鍗�',
+        // 閫夋嫨閿�鍞鍗曞悗锛岃嚜鍔ㄥ洖濉鎴蜂俊鎭�
+        onChange: (item: any) => {
+          if (item?.customerId && formApi) {
+            formApi.setFieldValue('clientId', item.customerId);
+            formApi.setFieldValue('clientName', item.customerName);
+          }
+        },
+      },
+    },
+    {
+      fieldName: 'clientName',
+      label: '',
+      component: 'Input',
+      dependencies: {
+        triggerFields: [''],
+        show: () => false,
       },
     },
     {
@@ -92,6 +113,15 @@
       component: markRaw(MdClientSelect),
       componentProps: {
         placeholder: '璇烽�夋嫨瀹㈡埛',
+        disabled: true,
+      },
+      dependencies: {
+        triggerFields: ['clientName'],
+        componentProps: (values) => ({
+          placeholder: '璇烽�夋嫨瀹㈡埛',
+          disabled: true,
+          clientName: values.clientName as string,
+        }),
       },
       rules: 'selectRequired',
     },
@@ -240,6 +270,25 @@
       },
     },
     {
+      field: 'outStatus',
+      title: '鍑哄簱鐘舵��',
+      minWidth: 100,
+      cellRender: {
+        name: 'CellTag',
+        props: {
+          formatter: ({ row }: { row: MesWmSalesNoticeApi.SalesNotice }) => {
+            const statusMap: Record<number, { color: string; label: string }> = {
+              [MesWmSalesNoticeOutStatusEnum.NONE]: { color: 'default', label: '鏈嚭搴�' },
+              [MesWmSalesNoticeOutStatusEnum.PARTIAL]: { color: 'warning', label: '閮ㄥ垎鍑哄簱' },
+              [MesWmSalesNoticeOutStatusEnum.ALL]: { color: 'success', label: '鍏ㄩ儴鍑哄簱' },
+            };
+            const status = statusMap[row.outStatus ?? MesWmSalesNoticeOutStatusEnum.NONE];
+            return { color: status?.color, label: status?.label ?? '鏈嚭搴�' };
+          },
+        },
+      },
+    },
+    {
       title: '鎿嶄綔',
       width: 200,
       fixed: 'right',
@@ -279,6 +328,28 @@
       width: 100,
     },
     {
+      field: 'outCount',
+      title: '宸插嚭搴撴暟閲�',
+      width: 100,
+      formatter: ({ cellValue }: { cellValue: number }) => (cellValue || 0).toFixed(2),
+    },
+    {
+      field: 'outProgress',
+      title: '鍑哄簱杩涘害',
+      width: 120,
+      cellRender: {
+        name: 'CellText',
+        props: {
+          formatter: ({ row }: { row: MesWmSalesNoticeLineApi.SalesNoticeLine }) => {
+            const quantity = row.quantity || 0;
+            const outCount = row.outCount || 0;
+            const percent = quantity > 0 ? Math.round((outCount / quantity) * 100) : 0;
+            return `${outCount.toFixed(2)} / ${quantity.toFixed(2)} (${percent}%)`;
+          },
+        },
+      },
+    },
+    {
       field: 'batchCode',
       title: '鎵规鍙�',
       minWidth: 120,
diff --git a/src/views/wls/salesnotice/index.vue b/src/views/wls/salesnotice/index.vue
index cd323e8..db5f8a5 100644
--- a/src/views/wls/salesnotice/index.vue
+++ b/src/views/wls/salesnotice/index.vue
@@ -8,10 +8,13 @@
 
 import { Button, message } from 'ant-design-vue';
 
+import { useRouter } from 'vue-router';
+
 import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
 import {
   deleteSalesNotice,
   exportSalesNotice,
+  generateProductSales,
   getSalesNoticePage,
 } from '#/api/mes/wm/salesnotice';
 import { $t } from '#/locales';
@@ -23,6 +26,8 @@
   connectedComponent: Form,
   destroyOnClose: true,
 });
+
+const router = useRouter();
 
 /** 鍒锋柊琛ㄦ牸 */
 function handleRefresh() {
@@ -47,6 +52,21 @@
 /** 鎵ц鍑哄簱 */
 function handleFinish(row: MesWmSalesNoticeApi.SalesNotice) {
   formModalApi.setData({ formType: 'finish', id: row.id }).open();
+}
+
+/** 鐢熸垚閿�鍞嚭搴撳崟 */
+async function handleGenerateProductSales(row: MesWmSalesNoticeApi.SalesNotice) {
+  try {
+    const productSalesId = await generateProductSales(row.id!);
+    message.success('鐢熸垚閿�鍞嚭搴撳崟鎴愬姛');
+    // 璺宠浆鍒伴攢鍞嚭搴撳崟椤甸潰
+    router.push({
+      path: '/wls/product-sales',
+      query: { id: productSalesId },
+    });
+  } catch (e) {
+    console.error(e);
+  }
 }
 
 /** 鍒犻櫎鍙戣揣閫氱煡鍗� */
@@ -154,6 +174,14 @@
               },
             },
             {
+              label: '鐢熸垚鍑哄簱鍗�',
+              type: 'link',
+              icon: 'ant-design:file-protect-outlined',
+              auth: ['mes:wm-product-sales:create'],
+              ifShow: row.status === MesWmSalesNoticeStatusEnum.APPROVED,
+              onClick: handleGenerateProductSales.bind(null, row),
+            },
+            {
               label: '鎵ц鍑哄簱',
               type: 'link',
               auth: ['mes:wm-sales-notice:update'],

--
Gitblit v1.9.3