From 5f708178b260cabf081f9049cf51e9ad66ae2911 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期四, 10 九月 2026 14:49:29 +0800
Subject: [PATCH] feat(erp/mes): 新增采购订单/销售订单/计量器具 Excel 导入,含导入菜单权限 SQL

---
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/ErpSaleOrderController.java                     |   30 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportExcelVO.java       |   81 ++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java                             |  182 +++++++++
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java                                      |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java                     |  179 ++++++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportRespVO.java        |   29 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportRespVO.java  |   29 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java                         |   10 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportRespVO.java          |   29 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java                     |  173 ++++++++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportExcelVO.java         |   71 +++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java                         |   11 
 yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java                                  |    6 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java             |   27 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportExcelVO.java |   67 +++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java                        |   18 
 sql/mysql/erp_purchase_order_import_menu.sql                                                                                     |   23 +
 yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java                                  |    9 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java                                 |   10 
 sql/mysql/import_menu_patch.sql                                                                                                  |   77 +++
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java             |   31 +
 sql/mysql/hrm_employee_contract_import_menu.sql                                                                                  |   23 
 sql/mysql/mes_dv_metering_import_menu.sql                                                                                        |   20 +
 sql/mysql/erp_sale_order_import_menu.sql                                                                                         |   20 +
 24 files changed, 1,154 insertions(+), 10 deletions(-)

diff --git a/sql/mysql/erp_purchase_order_import_menu.sql b/sql/mysql/erp_purchase_order_import_menu.sql
new file mode 100644
index 0000000..08237a6
--- /dev/null
+++ b/sql/mysql/erp_purchase_order_import_menu.sql
@@ -0,0 +1,23 @@
+-- 閲囪喘璁㈠崟 - 瀵煎叆鎸夐挳鏉冮檺锛堢埗鑿滃崟锛氶噰璐鍗� id=2666锛�
+-- 淇璇存槑锛氬師鑴氭湰鍐欐 id=602511锛屼笌鈥滅敓浜ц鍒�-鏂板(mes:pro-mps:create)鈥濇挒杞﹀鑷� INSERT 琚烦杩囥��
+--          鐜版敼涓哄姩鎬佸彇 system_menu 鏈�澶� id + 1锛屽苟鐢ㄥ彉閲忛鍒わ紝鍙噸澶嶆墽琛岋紙骞傜瓑锛夈��
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'erp:purchase-order:import');
+SET @import_menu_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+
+-- 1) 鏂板鈥滈噰璐鍗曞鍏モ�濇寜閽潈闄愶紙宸插瓨鍦ㄥ垯涓嶆彃锛�
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @import_menu_id, '閲囪喘璁㈠崟瀵煎叆', 'erp:purchase-order:import', 3, 8, 2666, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @perm_exists = 0;
+
+-- 2) 鎺堜簣瓒呯骇绠$悊鍛樿鑹诧紙role_id=1锛夎鎸夐挳鏉冮檺锛堝凡鎺堟潈鍒欎笉鎻掞級
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'erp:purchase-order:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
diff --git a/sql/mysql/erp_sale_order_import_menu.sql b/sql/mysql/erp_sale_order_import_menu.sql
new file mode 100644
index 0000000..232055d
--- /dev/null
+++ b/sql/mysql/erp_sale_order_import_menu.sql
@@ -0,0 +1,20 @@
+-- 閿�鍞鍗� - 瀵煎叆鎸夐挳鏉冮檺锛堢埗鑿滃崟锛氶攢鍞鍗� id=2638锛�
+-- 鐢ㄥ姩鎬� id锛圡AX(id)+1锛夛紝閬垮厤涓庢棦鏈夎彍鍗曟挒杞︼紱鍙噸澶嶆墽琛岋紙骞傜瓑锛夈��
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'erp:sale-order:import');
+SET @import_menu_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @import_menu_id, '閿�鍞鍗曞鍏�', 'erp:sale-order:import', 3, 8, 2638, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @perm_exists = 0;
+
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'erp:sale-order:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
diff --git a/sql/mysql/hrm_employee_contract_import_menu.sql b/sql/mysql/hrm_employee_contract_import_menu.sql
index d47f209..1d553be 100644
--- a/sql/mysql/hrm_employee_contract_import_menu.sql
+++ b/sql/mysql/hrm_employee_contract_import_menu.sql
@@ -1,13 +1,20 @@
--- 鍛樺伐鍚堝悓 - 瀵煎叆/瀵煎叆妯℃澘 鎸夐挳鏉冮檺
--- 鐖惰彍鍗曪細鍛樺伐鍚堝悓锛坧arent_id = 1063105锛�
--- 鍙傜収锛氬悎鍚屾柊澧炴寜閽� id=1063107锛坱ype=3, permission=hrm:employee-contract:create锛�
+-- 鍛樺伐鍚堝悓 - 瀵煎叆鎸夐挳鏉冮檺锛堢埗鑿滃崟锛氬憳宸ュ悎鍚� id=1063105锛�
+-- 鍔ㄦ�� id锛圡AX(id)+1锛夛紝閬垮厤涓庢棦鏈夎彍鍗曟挒杞︼紱鍙噸澶嶆墽琛岋紙骞傜瓑锛夈��
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'hrm:employee-contract:import');
+SET @import_menu_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+
 INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
                          component_name, status, visible, keep_alive, always_show,
                          creator, create_time, updater, update_time, deleted)
-VALUES (1063300, '鍚堝悓瀵煎叆', 'hrm:employee-contract:import', 3, 5, 1063105, '', '#', '', NULL,
-        0, 1, 0, 1, '1', NOW(), '1', NOW(), 0);
+SELECT @import_menu_id, '鍚堝悓瀵煎叆', 'hrm:employee-contract:import', 3, 5, 1063105, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @perm_exists = 0;
 
--- 鎺堜簣瓒呯骇绠$悊鍛樿鑹诧紙role_id=1锛夎鎸夐挳鏉冮檺锛堝箓绛夛級
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'hrm:employee-contract:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+
 INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
-SELECT 1, 1063300, '1', NOW(), '1', NOW(), 0 FROM DUAL
-WHERE NOT EXISTS (SELECT 1 FROM system_role_menu WHERE role_id = 1 AND menu_id = 1063300);
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
diff --git a/sql/mysql/import_menu_patch.sql b/sql/mysql/import_menu_patch.sql
new file mode 100644
index 0000000..c98af84
--- /dev/null
+++ b/sql/mysql/import_menu_patch.sql
@@ -0,0 +1,77 @@
+-- =====================================================================
+-- 鍥涙ā鍧椻�滃鍏モ�濇寜閽潈闄� 鍚堝苟琛ヤ竵
+--   1) 閲囪喘璁㈠崟瀵煎叆  erp:purchase-order:import
+--   2) 閿�鍞鍗曞鍏�  erp:sale-order:import
+--   3) 璁¢噺鍣ㄥ叿瀵煎叆  mes:dv-metering:import
+--   4) 鍛樺伐鍚堝悓瀵煎叆  hrm:employee-contract:import
+--
+-- 鐢ㄦ硶锛氬湪銆愮洰鏍囨暟鎹簱銆戜笂鏁翠綋鎵ц涓�娆″嵆鍙紙鏈湴寮�鍙戝簱 / 杩滅▼娴嬭瘯搴� / 鐢熶骇搴擄級銆�
+-- 鐗圭偣锛�
+--   * 鍔ㄦ�佸彇 system_menu 鏈�澶� id + 1锛屼笉浼氫笌鏃㈡湁鑿滃崟 id 鎾炶溅锛�
+--   * 鎸� permission 鍒ゆ柇锛屽箓绛夛紝鍙噸澶嶆墽琛岋紱
+--   * 浠呮巿鏉冣�滆秴绾х鐞嗗憳鈥濊鑹� role_id=1锛涘瑕佽鍏跺畠瑙掕壊涔熻兘瀵煎叆锛�
+--     璇峰湪銆愮郴缁熺鐞� 鈫� 瑙掕壊绠$悊銆戦噷鍕鹃�夊搴斺�淴X瀵煎叆鈥濇寜閽��
+-- 娉ㄦ剰锛�
+--   * parent_id 涓哄悇妯″潡鑿滃崟 id锛堥噰璐鍗�=2666銆侀攢鍞鍗�=2638銆佽閲忓櫒鍏�=5380銆佸憳宸ュ悎鍚�=1063105锛夈��
+--     鑻ョ洰鏍囧簱鑿滃崟缁撴瀯琚敼杩囷紝璇峰厛鏍稿杩欏嚑涓埗鑿滃崟 id 鍐嶆墽琛屻��
+-- =====================================================================
+
+-- ------------- 1) 閲囪喘璁㈠崟瀵煎叆 -------------
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'erp:purchase-order:import');
+SET @new_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @new_id, '閲囪喘璁㈠崟瀵煎叆', 'erp:purchase-order:import', 3, 8, 2666, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL WHERE @perm_exists = 0;
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'erp:purchase-order:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0 FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
+
+-- ------------- 2) 閿�鍞鍗曞鍏� -------------
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'erp:sale-order:import');
+SET @new_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @new_id, '閿�鍞鍗曞鍏�', 'erp:sale-order:import', 3, 8, 2638, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL WHERE @perm_exists = 0;
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'erp:sale-order:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0 FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
+
+-- ------------- 3) 璁¢噺鍣ㄥ叿瀵煎叆 -------------
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'mes:dv-metering:import');
+SET @new_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @new_id, '璁¢噺鍣ㄥ叿瀵煎叆', 'mes:dv-metering:import', 3, 8, 5380, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL WHERE @perm_exists = 0;
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'mes:dv-metering:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0 FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
+
+-- ------------- 4) 鍛樺伐鍚堝悓瀵煎叆 -------------
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'hrm:employee-contract:import');
+SET @new_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @new_id, '鍚堝悓瀵煎叆', 'hrm:employee-contract:import', 3, 5, 1063105, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL WHERE @perm_exists = 0;
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'hrm:employee-contract:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0 FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
diff --git a/sql/mysql/mes_dv_metering_import_menu.sql b/sql/mysql/mes_dv_metering_import_menu.sql
new file mode 100644
index 0000000..03fc712
--- /dev/null
+++ b/sql/mysql/mes_dv_metering_import_menu.sql
@@ -0,0 +1,20 @@
+-- 璁¢噺鍣ㄥ叿 - 瀵煎叆鎸夐挳鏉冮檺锛堢埗鑿滃崟锛氳閲忓櫒鍏� id=5380锛�
+-- 鍔ㄦ�� id锛圡AX(id)+1锛夛紝閬垮厤涓庢棦鏈夎彍鍗曟挒杞︼紱鍙噸澶嶆墽琛岋紙骞傜瓑锛夈��
+SET @perm_exists := (SELECT COUNT(*) FROM system_menu WHERE permission = 'mes:dv-metering:import');
+SET @import_menu_id := IFNULL((SELECT MAX(id) FROM system_menu), 0) + 1;
+
+INSERT INTO system_menu (id, name, permission, type, sort, parent_id, path, icon, component,
+                         component_name, status, visible, keep_alive, always_show,
+                         creator, create_time, updater, update_time, deleted)
+SELECT @import_menu_id, '璁¢噺鍣ㄥ叿瀵煎叆', 'mes:dv-metering:import', 3, 8, 5380, '', '#', '', NULL,
+       0, 1, 0, 1, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @perm_exists = 0;
+
+SET @menu_id := (SELECT id FROM system_menu WHERE permission = 'mes:dv-metering:import');
+SET @grant_exists := (SELECT COUNT(*) FROM system_role_menu WHERE role_id = 1 AND menu_id = @menu_id);
+
+INSERT INTO system_role_menu (role_id, menu_id, creator, create_time, updater, update_time, deleted)
+SELECT 1, @menu_id, '1', NOW(), '1', NOW(), 0
+FROM DUAL
+WHERE @menu_id IS NOT NULL AND @grant_exists = 0;
diff --git a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
index 357d897..7572c06 100644
--- a/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
+++ b/yudao-module-crm-api/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
@@ -56,4 +56,13 @@
      */
     List<Long> getPermittedCustomerIds();
 
+    /**
+     * 鑾峰緱瀹㈡埛绠�鍗曞垪琛紙缂栧彿 + 鍚嶇О锛�
+     *
+     * 鐢ㄤ簬鍏跺畠妯″潡锛堝 ERP 閿�鍞鍗曞鍏ワ級鎸夊鎴峰悕绉板尮閰嶅鎴风紪鍙�
+     *
+     * @return 瀹㈡埛绠�鍗曞垪琛�
+     */
+    List<CrmCustomerRespDTO> getCustomerSimpleList();
+
 }
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
index 33bc0c5..7e34575 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApi.java
@@ -56,4 +56,13 @@
      */
     List<Long> getPermittedCustomerIds();
 
+    /**
+     * 鑾峰緱瀹㈡埛绠�鍗曞垪琛紙缂栧彿 + 鍚嶇О锛�
+     *
+     * 鐢ㄤ簬鍏跺畠妯″潡锛堝 ERP 閿�鍞鍗曞鍏ワ級鎸夊鎴峰悕绉板尮閰嶅鎴风紪鍙�
+     *
+     * @return 瀹㈡埛绠�鍗曞垪琛�
+     */
+    List<CrmCustomerRespDTO> getCustomerSimpleList();
+
 }
\ No newline at end of file
diff --git a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java
index 927cd1e..c839a1d 100644
--- a/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java
+++ b/yudao-module-crm/src/main/java/cn/iocoder/yudao/module/crm/api/customer/CrmCustomerApiImpl.java
@@ -121,6 +121,12 @@
         return new ArrayList<>(permittedIds);
     }
 
+    @Override
+    public List<CrmCustomerRespDTO> getCustomerSimpleList() {
+        List<CrmCustomerDO> customers = customerService.getCustomerList();
+        return BeanUtils.toBean(customers, CrmCustomerRespDTO.class);
+    }
+
     /**
      * 杩囨护鎺夌敤鎴锋病鏈夋潈闄愮殑瀹㈡埛
      * <p>
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 f014f62..ccebe86 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
@@ -11,6 +11,8 @@
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderRespVO;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportRespVO;
 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.ErpAuditStatus;
@@ -35,10 +37,12 @@
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Arrays;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -168,6 +172,29 @@
         ExcelUtils.write(response, "閲囪喘璁㈠崟.xls", "鏁版嵁", ErpPurchaseOrderRespVO.class, list);
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱瀵煎叆閲囪喘璁㈠崟妯℃澘")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:import')")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<ErpPurchaseOrderImportExcelVO> list = Arrays.asList(
+                ErpPurchaseOrderImportExcelVO.builder()
+                        .no("CGDD20260101000001").supplierName("绀轰緥渚涘簲鍟�")
+                        .orderTime("2026-01-01 10:00:00").productCode("ITEM001").productName("绀轰緥浜у搧A")
+                        .count(new BigDecimal("100")).productPrice(new BigDecimal("10.00"))
+                        .taxPercent(new BigDecimal("13")).qcCheckFlag("鍚�").remark("绀轰緥").build());
+        ExcelUtils.write(response, "閲囪喘璁㈠崟瀵煎叆妯℃澘.xls", "閲囪喘璁㈠崟", ErpPurchaseOrderImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆閲囪喘璁㈠崟")
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:import')")
+    public CommonResult<ErpPurchaseOrderImportRespVO> importPurchaseOrderExcel(
+            @RequestParam("file") MultipartFile file,
+            @RequestParam(value = "directApprove", required = false, defaultValue = "false") Boolean directApprove) throws Exception {
+        List<ErpPurchaseOrderImportExcelVO> list = ExcelUtils.read(file, ErpPurchaseOrderImportExcelVO.class);
+        return success(purchaseOrderService.importPurchaseOrderList(list, directApprove));
+    }
+
     @PostMapping("/confirm-receipt")
     @Operation(summary = "纭鏀惰揣")
     @Parameter(name = "id", description = "閲囪喘璁㈠崟缂栧彿", required = true)
@@ -219,7 +246,9 @@
                 .getPurchaseInvoicePriceSumMapByOrderIds(convertSet(pageResult.getList(), ErpPurchaseOrderDO::getId));
         // 2. 寮�濮嬫嫾鎺�
         return BeanUtils.toBean(pageResult, ErpPurchaseOrderRespVO.class, purchaseOrder -> {
-            purchaseOrder.setItems(BeanUtils.toBean(purchaseOrderItemMap.get(purchaseOrder.getId()), ErpPurchaseOrderRespVO.Item.class,
+            purchaseOrder.setItems(BeanUtils.toBean(
+                    purchaseOrderItemMap.getOrDefault(purchaseOrder.getId(), java.util.Collections.<ErpPurchaseOrderItemDO>emptyList()),
+                    ErpPurchaseOrderRespVO.Item.class,
                     item -> {
                         MapUtils.findAndThen(itemMap, item.getProductId(), mdmItem -> item.setProductName(mdmItem.getName())
                                 .setProductBarCode(mdmItem.getBarCode()));
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportExcelVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportExcelVO.java
new file mode 100644
index 0000000..638a35f
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportExcelVO.java
@@ -0,0 +1,67 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 閲囪喘璁㈠崟瀵煎叆 Excel VO
+ *
+ * 璇存槑锛氫竴琛� = 涓�鏉′骇鍝佹槑缁嗭紱鐩稿悓鈥滈噰璐崟缂栧彿鈥濈殑澶氳浼氬悎骞朵负涓�寮犺鍗曪紙涓昏〃瀛楁鍙栬缁勯琛岋級銆�
+ * 鈥滈噰璐崟缂栧彿鈥濈暀绌烘椂锛屾瘡涓�琛屼細鍚勮嚜鐢熸垚涓�寮犳柊璁㈠崟锛堝崟鏉℃槑缁嗭級銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘璁㈠崟瀵煎叆 Excel VO")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ErpPurchaseOrderImportExcelVO {
+
+    @ExcelProperty("閲囪喘鍗曠紪鍙�")
+    @Schema(description = "閲囪喘鍗曠紪鍙凤紝鐣欑┖鍒欒嚜鍔ㄧ敓鎴愶紱鐩稿悓缂栧彿鐨勫琛屽悎骞朵负鍚屼竴寮犺鍗�")
+    private String no;
+
+    @ExcelProperty("渚涘簲鍟嗗悕绉�")
+    @Schema(description = "渚涘簲鍟嗗悕绉帮紝闇�涓庣郴缁熷唴渚涘簲鍟嗗悕绉帮紙鎴栫紪鐮侊級涓�鑷�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String supplierName;
+
+    @ExcelProperty("閲囪喘鏃堕棿")
+    @Schema(description = "閲囪喘鏃堕棿锛屾牸寮忓 2026-01-01 10:00:00锛涚暀绌哄彇褰撳墠鏃堕棿")
+    private String orderTime;
+
+    @ExcelProperty("浜у搧缂栫爜")
+    @Schema(description = "浜у搧锛堢墿鏂欙級缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String productCode;
+
+    @ExcelProperty("浜у搧鍚嶇О")
+    @Schema(description = "浜у搧鍚嶇О锛堜粎渚涘鐓э紝鍖归厤浠嶄互浜у搧缂栫爜涓哄噯锛�")
+    private String productName;
+
+    @ExcelProperty("鏁伴噺")
+    @Schema(description = "浜у搧鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BigDecimal count;
+
+    @ExcelProperty("鍗曚环")
+    @Schema(description = "浜у搧鍗曚环")
+    private BigDecimal productPrice;
+
+    @ExcelProperty("绋庣巼(%)")
+    @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮锛屼緥濡� 13 琛ㄧず 13%")
+    private BigDecimal taxPercent;
+
+    @ExcelProperty("鏄惁鏉ユ枡妫�楠�")
+    @Schema(description = "鏄惁闇�瑕佹潵鏂欐楠岋細濉�滄槸鈥濅负闇�瑕侊紝鍏跺畠涓轰笉闇�瑕�")
+    private String qcCheckFlag;
+
+    @ExcelProperty("澶囨敞")
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportRespVO.java
new file mode 100644
index 0000000..e1dc7f9
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/order/ErpPurchaseOrderImportRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绠$悊鍚庡彴 - 閲囪喘璁㈠崟瀵煎叆 Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 閲囪喘璁㈠崟瀵煎叆 Response VO")
+@Data
+@Builder
+public class ErpPurchaseOrderImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勯噰璐崟缂栧彿鏁扮粍", example = "CGDD20260101000001")
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勯噰璐崟缂栧彿鏁扮粍", example = "CGDD20260101000002")
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触闆嗗悎锛宬ey 涓洪噰璐崟缂栧彿鎴栬鍙凤紝value 涓哄け璐ュ師鍥�")
+    private Map<String, String> failureList;
+
+}
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 5bf0421..d08ac09 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
@@ -14,6 +14,8 @@
 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;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportRespVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
 import cn.iocoder.yudao.module.erp.service.sale.ErpSaleOrderService;
@@ -37,10 +39,12 @@
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.math.BigDecimal;
 import java.util.List;
+import java.util.Arrays;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
@@ -206,6 +210,28 @@
         ExcelUtils.write(response, "閿�鍞鍗�.xls", "鏁版嵁", ErpSaleOrderRespVO.class, list);
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱瀵煎叆閿�鍞鍗曟ā鏉�")
+    @PreAuthorize("@ss.hasPermission('erp:sale-order:import')")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<ErpSaleOrderImportExcelVO> list = Arrays.asList(
+                ErpSaleOrderImportExcelVO.builder()
+                        .no("XSDD20260101000001").customerName("绀轰緥瀹㈡埛").contractNo("HT20260101")
+                        .orderTime("2026-01-01 10:00:00").productCode("ITEM001").productName("绀轰緥浜у搧A")
+                        .count(new BigDecimal("100")).productPrice(new BigDecimal("20.00"))
+                        .taxPercent(new BigDecimal("13")).needProduction("鏄�").remark("绀轰緥").build());
+        ExcelUtils.write(response, "閿�鍞鍗曞鍏ユā鏉�.xls", "閿�鍞鍗�", ErpSaleOrderImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆閿�鍞鍗�")
+    @PreAuthorize("@ss.hasPermission('erp:sale-order:import')")
+    public CommonResult<ErpSaleOrderImportRespVO> importSaleOrderExcel(
+            @RequestParam("file") MultipartFile file) throws Exception {
+        List<ErpSaleOrderImportExcelVO> list = ExcelUtils.read(file, ErpSaleOrderImportExcelVO.class);
+        return success(saleOrderService.importSaleOrderList(list));
+    }
+
     private PageResult<ErpSaleOrderRespVO> buildSaleOrderVOPageResult(PageResult<ErpSaleOrderDO> pageResult) {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty(pageResult.getTotal());
@@ -268,7 +294,9 @@
         }
         // 2. 寮�濮嬫嫾鎺�
         return BeanUtils.toBean(pageResult, ErpSaleOrderRespVO.class, saleOrder -> {
-            saleOrder.setItems(BeanUtils.toBean(saleOrderItemMap.get(saleOrder.getId()), ErpSaleOrderRespVO.Item.class,
+            saleOrder.setItems(BeanUtils.toBean(
+                    saleOrderItemMap.getOrDefault(saleOrder.getId(), java.util.Collections.<ErpSaleOrderItemDO>emptyList()),
+                    ErpSaleOrderRespVO.Item.class,
                     item -> {
                         fillItemPrice(item);
                         item.setBatchCode(batchCodeMap.get(item.getId()));
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportExcelVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportExcelVO.java
new file mode 100644
index 0000000..03527a9
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportExcelVO.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * 绠$悊鍚庡彴 - 閿�鍞鍗曞鍏� Excel VO
+ *
+ * 璇存槑锛氫竴琛� = 涓�鏉′骇鍝佹槑缁嗭紱鐩稿悓鈥滈攢鍞崟缂栧彿鈥濈殑澶氳浼氬悎骞朵负涓�寮犺鍗曪紙涓昏〃瀛楁鍙栬缁勯琛岋級銆�
+ * 鈥滈攢鍞崟缂栧彿鈥濈暀绌烘椂锛屾瘡涓�琛屼細鍚勮嚜鐢熸垚涓�寮犳柊璁㈠崟锛堝崟鏉℃槑缁嗭級銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 閿�鍞鍗曞鍏� Excel VO")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class ErpSaleOrderImportExcelVO {
+
+    @ExcelProperty("閿�鍞崟缂栧彿")
+    @Schema(description = "閿�鍞崟缂栧彿锛岀暀绌哄垯鑷姩鐢熸垚锛涚浉鍚岀紪鍙风殑澶氳鍚堝苟涓哄悓涓�寮犺鍗�")
+    private String no;
+
+    @ExcelProperty("瀹㈡埛鍚嶇О")
+    @Schema(description = "瀹㈡埛鍚嶇О锛岄渶涓� CRM 涓殑瀹㈡埛鍚嶇О涓�鑷�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String customerName;
+
+    @ExcelProperty("鍚堝悓鍗曞彿")
+    @Schema(description = "鍏宠仈鍚堝悓鍗曞彿锛屽彲绌�")
+    private String contractNo;
+
+    @ExcelProperty("涓嬪崟鏃堕棿")
+    @Schema(description = "涓嬪崟鏃堕棿锛屾牸寮忓 2026-01-01 10:00:00锛涚暀绌哄彇褰撳墠鏃堕棿")
+    private String orderTime;
+
+    @ExcelProperty("浜у搧缂栫爜")
+    @Schema(description = "浜у搧锛堢墿鏂欙級缂栫爜", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String productCode;
+
+    @ExcelProperty("浜у搧鍚嶇О")
+    @Schema(description = "浜у搧鍚嶇О锛堜粎渚涘鐓э紝鍖归厤浠嶄互浜у搧缂栫爜涓哄噯锛�")
+    private String productName;
+
+    @ExcelProperty("鏁伴噺")
+    @Schema(description = "浜у搧鏁伴噺", requiredMode = Schema.RequiredMode.REQUIRED)
+    private BigDecimal count;
+
+    @ExcelProperty("鍗曚环")
+    @Schema(description = "浜у搧鍗曚环锛涚暀绌哄垯鍙栬鐗╂枡鐨勯攢鍞环")
+    private BigDecimal productPrice;
+
+    @ExcelProperty("绋庣巼(%)")
+    @Schema(description = "绋庣巼锛岀櫨鍒嗘瘮锛屼緥濡� 13 琛ㄧず 13%")
+    private BigDecimal taxPercent;
+
+    @ExcelProperty("鏄惁闇�瑕佺敓浜�")
+    @Schema(description = "鏄惁闇�瑕佺敓浜э細濉�滄槸/鍚︹�濓紝鐣欑┖榛樿鈥滄槸鈥�")
+    private String needProduction;
+
+    @ExcelProperty("澶囨敞")
+    @Schema(description = "澶囨敞锛涘悓涓�寮犺鍗曞彇棣栬澶囨敞浣滀负鏁村崟澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportRespVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportRespVO.java
new file mode 100644
index 0000000..97c52d5
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/sale/vo/order/ErpSaleOrderImportRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绠$悊鍚庡彴 - 閿�鍞鍗曞鍏� Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 閿�鍞鍗曞鍏� Response VO")
+@Data
+@Builder
+public class ErpSaleOrderImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勯攢鍞崟缂栧彿鏁扮粍", example = "XSDD20260101000001")
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勯攢鍞崟缂栧彿鏁扮粍", example = "XSDD20260101000002")
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触闆嗗悎锛宬ey 涓洪攢鍞崟缂栧彿鎴栬鍙凤紝value 涓哄け璐ュ師鍥�")
+    private Map<String, String> failureList;
+
+}
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 d5c204f..1bfd1ce 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
@@ -3,6 +3,8 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportRespVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
 import jakarta.validation.Valid;
@@ -28,6 +30,15 @@
     Long createPurchaseOrder(@Valid ErpPurchaseOrderSaveReqVO createReqVO);
 
     /**
+     * 瀵煎叆閲囪喘璁㈠崟
+     *
+     * @param importList    瀵煎叆鏁版嵁锛堟瘡涓�琛� = 涓�鏉℃槑缁嗭紝鐩稿悓閲囪喘鍗曠紪鍙峰悎骞朵负涓�寮犺鍗曪級
+     * @param directApprove 鏄惁灏嗗鍏ョ殑璁㈠崟鐩存帴缃负鈥滃凡瀹℃牳鈥濓細true=宸插鏍�(20)锛宖alse=鏈鏍�(10)
+     * @return 瀵煎叆缁撴灉
+     */
+    ErpPurchaseOrderImportRespVO importPurchaseOrderList(List<ErpPurchaseOrderImportExcelVO> importList, Boolean directApprove);
+
+    /**
      * 鏇存柊閲囪喘璁㈠崟
      *
      * @param updateReqVO 鏇存柊淇℃伅
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 8809791..4b51cfc 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
@@ -2,6 +2,8 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -14,6 +16,8 @@
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderImportRespVO;
 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.dal.dataobject.purchase.ErpPurchaseRequestDO;
@@ -26,6 +30,10 @@
 import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderInStatusEnum;
 import cn.iocoder.yudao.module.erp.service.finance.ErpAccountService;
+import cn.iocoder.yudao.module.mdm.api.item.MdmItemApi;
+import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
+import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
+import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
 import cn.iocoder.yudao.module.mes.api.arrivalnotice.MesArrivalNoticeApi;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import jakarta.annotation.Resource;
@@ -37,6 +45,9 @@
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.util.*;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -94,6 +105,11 @@
 
     @Resource
     private StorageAttachmentApi storageAttachmentApi;
+
+    @Resource
+    private SrmSupplierApi srmSupplierApi;
+    @Resource
+    private MdmItemApi mdmItemApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -159,6 +175,169 @@
         storageAttachmentApi.updateAttachments("file", "erp_purchase_order", updateReqVO.getId(), updateReqVO.getBlobIds());
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ErpPurchaseOrderImportRespVO importPurchaseOrderList(List<ErpPurchaseOrderImportExcelVO> importList,
+                                                               Boolean directApprove) {
+        ErpPurchaseOrderImportRespVO respVO = ErpPurchaseOrderImportRespVO.builder()
+                .createList(new ArrayList<>())
+                .updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>())
+                .build();
+        if (CollUtil.isEmpty(importList)) {
+            return respVO;
+        }
+
+        // 1. 棰勭儹鈥滀緵搴斿晢鍚嶇О/缂栫爜 -> 渚涘簲鍟嗙紪鍙封�濇槧灏勶紙ERP 渚涘簲鍟嗕富鏁版嵁鍦� SRM 妯″潡锛�
+        Map<String, Long> supplierIdMap = new HashMap<>();
+        CommonResult<List<SrmSupplierRespDTO>> supplierResult = srmSupplierApi.getSupplierSimpleList();
+        if (supplierResult != null && CollUtil.isNotEmpty(supplierResult.getData())) {
+            supplierResult.getData().forEach(supplier -> {
+                if (StrUtil.isNotBlank(supplier.getName())) {
+                    supplierIdMap.put(supplier.getName().trim(), supplier.getId());
+                }
+                if (StrUtil.isNotBlank(supplier.getCode())) {
+                    supplierIdMap.putIfAbsent(supplier.getCode().trim(), supplier.getId());
+                }
+            });
+        }
+
+        // 2. 鎸夆�滈噰璐崟缂栧彿鈥濆垎缁勶細鍚岀紪鍙峰琛� = 涓�寮犺鍗曠殑澶氭潯鏄庣粏锛涚紪鍙蜂负绌哄垯姣忚鐙珛鎴愬崟
+        Map<String, List<ErpPurchaseOrderImportExcelVO>> orderGroups = new LinkedHashMap<>();
+        for (int i = 0; i < importList.size(); i++) {
+            ErpPurchaseOrderImportExcelVO row = importList.get(i);
+            if (row == null || (StrUtil.isBlank(row.getSupplierName()) && StrUtil.isBlank(row.getProductCode())
+                    && StrUtil.isBlank(row.getNo()))) {
+                continue; // 璺宠繃绌鸿
+            }
+            String key = StrUtil.isNotBlank(row.getNo()) ? row.getNo().trim() : "__row_" + (i + 2);
+            orderGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
+        }
+
+        // 3. 閫愪釜璁㈠崟瀵煎叆锛堝崟寮犺鍗曞け璐ヤ笉褰卞搷鍏跺畠锛�
+        for (Map.Entry<String, List<ErpPurchaseOrderImportExcelVO>> entry : orderGroups.entrySet()) {
+            List<ErpPurchaseOrderImportExcelVO> rows = entry.getValue();
+            String label = StrUtil.isNotBlank(rows.get(0).getNo()) ? rows.get(0).getNo().trim()
+                    : "绗� " + entry.getKey().substring("__row_".length()) + " 琛�";
+            try {
+                ErpPurchaseOrderDO purchaseOrder = createPurchaseOrderByImport(rows, supplierIdMap, directApprove);
+                respVO.getCreateList().add(purchaseOrder.getNo());
+            } catch (Exception ex) {
+                respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
+            }
+        }
+        return respVO;
+    }
+
+    /**
+     * 渚濇嵁瀵煎叆鏁版嵁鍒涘缓涓�寮犻噰璐鍗曪紙鍚槑缁嗭級銆�
+     *
+     * 娉ㄦ剰锛氭澶勪笉璋冪敤 submitPurchaseOrder锛屽洜姝や笉浼氬垱寤� BPM 宸ヤ綔娴侊紱璁㈠崟鐘舵�佺敱 directApprove 鐩存帴鍐冲畾锛�
+     * 鍕鹃�夆�滄槸鍚﹀鏍糕�� => 宸插鏍�(20)锛屾湭鍕鹃�� => 鏈鏍�(10)銆�
+     */
+    private ErpPurchaseOrderDO createPurchaseOrderByImport(List<ErpPurchaseOrderImportExcelVO> rows,
+                                                           Map<String, Long> supplierIdMap, Boolean directApprove) {
+        // 1. 渚涘簲鍟嗭紙鍙栬缁勯琛岋級
+        ErpPurchaseOrderImportExcelVO head = rows.get(0);
+        if (StrUtil.isBlank(head.getSupplierName())) {
+            throw new IllegalArgumentException("渚涘簲鍟嗗悕绉颁笉鑳戒负绌�");
+        }
+        Long supplierId = supplierIdMap.get(head.getSupplierName().trim());
+        if (supplierId == null) {
+            throw new IllegalArgumentException("渚涘簲鍟嗕笉瀛樺湪锛�" + head.getSupplierName());
+        }
+        supplierService.validateSupplier(supplierId);
+
+        // 2. 鏄庣粏
+        List<ErpPurchaseOrderSaveReqVO.Item> items = new ArrayList<>();
+        for (ErpPurchaseOrderImportExcelVO row : rows) {
+            if (StrUtil.isBlank(row.getProductCode())) {
+                throw new IllegalArgumentException("浜у搧缂栫爜涓嶈兘涓虹┖");
+            }
+            CommonResult<MdmItemRespDTO> itemResult = mdmItemApi.getItemByCode(row.getProductCode().trim());
+            MdmItemRespDTO item = itemResult == null ? null : itemResult.getData();
+            if (item == null) {
+                throw new IllegalArgumentException("浜у搧涓嶅瓨鍦細" + row.getProductCode());
+            }
+            if (row.getCount() == null) {
+                throw new IllegalArgumentException("浜у搧[" + row.getProductCode() + "]鐨勬暟閲忎笉鑳戒负绌�");
+            }
+            if (item.getUnitMeasureId() == null) {
+                throw new IllegalArgumentException("浜у搧[" + row.getProductCode() + "]鏈厤缃閲忓崟浣嶏紝璇峰厛鍦ㄧ墿鏂欎腑缁存姢");
+            }
+            ErpPurchaseOrderSaveReqVO.Item itemVO = new ErpPurchaseOrderSaveReqVO.Item();
+            itemVO.setProductId(item.getId());
+            itemVO.setProductUnitId(item.getUnitMeasureId());
+            itemVO.setProductPrice(row.getProductPrice() == null ? BigDecimal.ZERO : row.getProductPrice());
+            itemVO.setCount(row.getCount());
+            itemVO.setTaxPercent(row.getTaxPercent());
+            itemVO.setRemark(row.getRemark());
+            itemVO.setQcCheckFlag("鏄�".equals(StrUtil.trim(row.getQcCheckFlag())));
+            items.add(itemVO);
+        }
+
+        // 3. 鍗曞彿锛氬~浜嗗氨鐢紙鏍¢獙鍞竴锛夛紝娌″~鑷姩鐢熸垚
+        String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
+                : noRedisDAO.generate(ErpNoRedisDAO.PURCHASE_ORDER_NO_PREFIX);
+        if (purchaseOrderMapper.selectByNo(no) != null) {
+            throw new IllegalArgumentException("閲囪喘鍗曠紪鍙峰凡瀛樺湪锛�" + no);
+        }
+
+        // 4. 缁勮涓昏〃锛氬嬀閫夆�滄槸鍚﹀鏍糕�� => 宸插鏍�(20)锛屽惁鍒欐湭瀹℃牳(10)
+        ErpPurchaseOrderDO purchaseOrder = new ErpPurchaseOrderDO();
+        purchaseOrder.setNo(no);
+        purchaseOrder.setSupplierId(supplierId);
+        purchaseOrder.setOrderTime(parseImportOrderTime(head.getOrderTime()));
+        purchaseOrder.setRemark(head.getRemark());
+        purchaseOrder.setStatus(Boolean.TRUE.equals(directApprove)
+                ? ErpAuditStatus.APPROVE.getStatus() : ErpAuditStatus.PROCESS.getStatus());
+        purchaseOrder.setDepositPrice(BigDecimal.ZERO); // deposit_price 涓� NOT NULL 涓旀棤榛樿鍊硷紝蹇呴』鏄惧紡璧嬪��
+        purchaseOrder.setInCount(BigDecimal.ZERO);
+        purchaseOrder.setInStatus(ErpPurchaseOrderInStatusEnum.NOT_IN.getStatus());
+
+        List<ErpPurchaseOrderItemDO> itemDOs = validatePurchaseOrderItems(items);
+        calculateTotalPrice(purchaseOrder, itemDOs);
+        purchaseOrderMapper.insert(purchaseOrder);
+        itemDOs.forEach(item -> item.setOrderId(purchaseOrder.getId()));
+        purchaseOrderItemMapper.insertBatch(itemDOs);
+        return purchaseOrder;
+    }
+
+    /**
+     * 瑙f瀽瀵煎叆鐨勯噰璐椂闂达紝鍏煎澶氱甯歌鏍煎紡锛涗负绌哄彇褰撳墠鏃堕棿
+     */
+    private LocalDateTime parseImportOrderTime(String text) {
+        if (StrUtil.isBlank(text)) {
+            return LocalDateTime.now();
+        }
+        String value = text.trim();
+        for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss"}) {
+            try {
+                return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern));
+            } catch (Exception ignored) {
+                // 缁х画灏濊瘯涓嬩竴涓牸寮�
+            }
+        }
+        for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+            try {
+                return LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay();
+            } catch (Exception ignored) {
+                // 缁х画灏濊瘯涓嬩竴涓牸寮�
+            }
+        }
+        throw new IllegalArgumentException("閲囪喘鏃堕棿鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01 10:00:00");
+    }
+
+    /**
+     * 鎻愬彇瀵煎叆澶辫触鍘熷洜锛圫erviceException 鍙栦笟鍔℃彁绀猴級
+     */
+    private String resolveImportErrorMessage(Exception ex) {
+        if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) {
+            return serviceException.getMessage();
+        }
+        return StrUtil.blankToDefault(ex.getMessage(), "瀵煎叆澶辫触");
+    }
+
     private void calculateTotalPrice(ErpPurchaseOrderDO purchaseOrder, List<ErpPurchaseOrderItemDO> purchaseOrderItems) {
         purchaseOrder.setTotalCount(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getCount, BigDecimal::add));
         purchaseOrder.setTotalProductPrice(getSumValue(purchaseOrderItems, ErpPurchaseOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
index cd8e54a..9d0fe8e 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderService.java
@@ -3,6 +3,8 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportRespVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
 import jakarta.validation.Valid;
@@ -28,6 +30,14 @@
     Long createSaleOrder(@Valid ErpSaleOrderSaveReqVO createReqVO);
 
     /**
+     * 瀵煎叆閿�鍞鍗曪紙瀵煎叆鍚庝负鈥滄湭鎻愪氦鈥濈姸鎬侊紝鍙户缁紪杈戞垨鎻愪氦瀹℃壒锛�
+     *
+     * @param importList 瀵煎叆鏁版嵁锛堟瘡涓�琛� = 涓�鏉℃槑缁嗭紝鐩稿悓閿�鍞崟缂栧彿鍚堝苟涓轰竴寮犺鍗曪級
+     * @return 瀵煎叆缁撴灉
+     */
+    ErpSaleOrderImportRespVO importSaleOrderList(List<ErpSaleOrderImportExcelVO> importList);
+
+    /**
      * 鏇存柊閿�鍞鍗�
      *
      * @param updateReqVO 鏇存柊淇℃伅
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
index 9a49e7c..18985aa 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java
@@ -2,6 +2,8 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.number.MoneyUtils;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -13,9 +15,12 @@
 import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
 import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
+import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
 import cn.iocoder.yudao.module.crm.api.contract.CrmContractApi;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderPageReqVO;
 import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderSaveReqVO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportExcelVO;
+import cn.iocoder.yudao.module.erp.controller.admin.sale.vo.order.ErpSaleOrderImportRespVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.sale.ErpSaleOrderItemDO;
 import cn.iocoder.yudao.module.erp.dal.mysql.sale.ErpSaleOrderItemMapper;
@@ -39,12 +44,15 @@
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.time.format.DateTimeFormatter;
 import java.time.format.DateTimeFormatter;
 import java.util.ArrayList;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.HashMap;
+import java.util.LinkedHashMap;
 import java.util.List;
 import java.util.Map;
 
@@ -180,6 +188,180 @@
         storageAttachmentApi.updateAttachments("file", "erp_sale_order", updateReqVO.getId(), updateReqVO.getBlobIds());
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public ErpSaleOrderImportRespVO importSaleOrderList(List<ErpSaleOrderImportExcelVO> importList) {
+        ErpSaleOrderImportRespVO respVO = ErpSaleOrderImportRespVO.builder()
+                .createList(new ArrayList<>())
+                .updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>())
+                .build();
+        if (CollUtil.isEmpty(importList)) {
+            return respVO;
+        }
+
+        // 1. 棰勭儹鈥滃鎴峰悕绉� -> 瀹㈡埛缂栧彿鈥濇槧灏勶紙瀹㈡埛涓绘暟鎹湪 CRM 妯″潡锛�
+        Map<String, Long> customerIdMap = new HashMap<>();
+        List<CrmCustomerRespDTO> customers = customerApi.getCustomerSimpleList();
+        if (CollUtil.isNotEmpty(customers)) {
+            customers.forEach(customer -> {
+                if (StrUtil.isNotBlank(customer.getName())) {
+                    customerIdMap.putIfAbsent(customer.getName().trim(), customer.getId());
+                }
+            });
+        }
+
+        // 2. 鎸夆�滈攢鍞崟缂栧彿鈥濆垎缁勶細鍚岀紪鍙峰琛� = 涓�寮犺鍗曠殑澶氭潯鏄庣粏锛涚紪鍙蜂负绌哄垯姣忚鐙珛鎴愬崟
+        Map<String, List<ErpSaleOrderImportExcelVO>> orderGroups = new LinkedHashMap<>();
+        for (int i = 0; i < importList.size(); i++) {
+            ErpSaleOrderImportExcelVO row = importList.get(i);
+            if (row == null || (StrUtil.isBlank(row.getCustomerName()) && StrUtil.isBlank(row.getProductCode())
+                    && StrUtil.isBlank(row.getNo()))) {
+                continue; // 璺宠繃绌鸿
+            }
+            String key = StrUtil.isNotBlank(row.getNo()) ? row.getNo().trim() : "__row_" + (i + 2);
+            orderGroups.computeIfAbsent(key, k -> new ArrayList<>()).add(row);
+        }
+
+        // 3. 閫愪釜璁㈠崟瀵煎叆锛堝崟寮犺鍗曞け璐ヤ笉褰卞搷鍏跺畠锛�
+        for (Map.Entry<String, List<ErpSaleOrderImportExcelVO>> entry : orderGroups.entrySet()) {
+            List<ErpSaleOrderImportExcelVO> rows = entry.getValue();
+            String label = StrUtil.isNotBlank(rows.get(0).getNo()) ? rows.get(0).getNo().trim()
+                    : "绗� " + entry.getKey().substring("__row_".length()) + " 琛�";
+            try {
+                ErpSaleOrderDO saleOrder = createSaleOrderByImport(rows, customerIdMap);
+                respVO.getCreateList().add(saleOrder.getNo());
+            } catch (Exception ex) {
+                respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
+            }
+        }
+        return respVO;
+    }
+
+    /**
+     * 渚濇嵁瀵煎叆鏁版嵁鍒涘缓涓�寮犻攢鍞鍗曪紙鍚槑缁嗭級銆�
+     *
+     * 娉ㄦ剰锛氭澶勪笉璋冪敤 submitSaleOrder锛屽洜姝や笉浼氬垱寤� BPM 宸ヤ綔娴侊紱璁㈠崟鐘舵�佸浐瀹氫负鈥滄湭鎻愪氦(0)鈥濓紝
+     * 鍙湪鍒楄〃涓户缁紪杈戞垨鎻愪氦瀹℃壒銆�
+     */
+    private ErpSaleOrderDO createSaleOrderByImport(List<ErpSaleOrderImportExcelVO> rows,
+                                                   Map<String, Long> customerIdMap) {
+        // 1. 瀹㈡埛锛堝彇璇ョ粍棣栬锛�
+        ErpSaleOrderImportExcelVO head = rows.get(0);
+        if (StrUtil.isBlank(head.getCustomerName())) {
+            throw new IllegalArgumentException("瀹㈡埛鍚嶇О涓嶈兘涓虹┖");
+        }
+        Long customerId = customerIdMap.get(head.getCustomerName().trim());
+        if (customerId == null) {
+            throw new IllegalArgumentException("瀹㈡埛涓嶅瓨鍦細" + head.getCustomerName());
+        }
+        customerApi.validateCustomer(customerId);
+
+        // 2. 鏄庣粏
+        List<ErpSaleOrderSaveReqVO.Item> items = new ArrayList<>();
+        for (ErpSaleOrderImportExcelVO row : rows) {
+            if (StrUtil.isBlank(row.getProductCode())) {
+                throw new IllegalArgumentException("浜у搧缂栫爜涓嶈兘涓虹┖");
+            }
+            CommonResult<MdmItemRespDTO> itemResult = mdmItemApi.getItemByCode(row.getProductCode().trim());
+            MdmItemRespDTO item = itemResult == null ? null : itemResult.getData();
+            if (item == null) {
+                throw new IllegalArgumentException("浜у搧涓嶅瓨鍦細" + row.getProductCode());
+            }
+            if (row.getCount() == null) {
+                throw new IllegalArgumentException("浜у搧[" + row.getProductCode() + "]鐨勬暟閲忎笉鑳戒负绌�");
+            }
+            if (item.getUnitMeasureId() == null) {
+                throw new IllegalArgumentException("浜у搧[" + row.getProductCode() + "]鏈厤缃閲忓崟浣嶏紝璇峰厛鍦ㄧ墿鏂欎腑缁存姢");
+            }
+            ErpSaleOrderSaveReqVO.Item itemVO = new ErpSaleOrderSaveReqVO.Item();
+            itemVO.setProductId(item.getId());
+            itemVO.setProductUnitId(item.getUnitMeasureId());
+            // 鍗曚环锛氭湭濉啓鏃跺彇鐗╂枡鐨勯攢鍞环锛堜笌椤甸潰閫変骇鍝佸悗鐨勯粯璁ゅ�间竴鑷达級
+            itemVO.setProductPrice(row.getProductPrice() != null ? row.getProductPrice()
+                    : ObjectUtil.defaultIfNull(item.getSalesPrice(), BigDecimal.ZERO));
+            itemVO.setCount(row.getCount());
+            itemVO.setTaxPercent(row.getTaxPercent());
+            itemVO.setNeedProduction(parseNeedProduction(row.getNeedProduction()));
+            itemVO.setRemark(row.getRemark());
+            items.add(itemVO);
+        }
+
+        // 3. 鍗曞彿锛氬~浜嗗氨鐢紙鏍¢獙鍞竴锛夛紝娌″~鑷姩鐢熸垚
+        String no = StrUtil.isNotBlank(head.getNo()) ? head.getNo().trim()
+                : noRedisDAO.generate(ErpNoRedisDAO.SALE_ORDER_NO_PREFIX);
+        if (saleOrderMapper.selectByNo(no) != null) {
+            throw new IllegalArgumentException("閿�鍞崟缂栧彿宸插瓨鍦細" + no);
+        }
+
+        // 4. 缁勮涓昏〃锛氬鍏ュ悗鍥哄畾涓衡�滄湭鎻愪氦(0)鈥濈姸鎬�
+        ErpSaleOrderDO saleOrder = new ErpSaleOrderDO();
+        saleOrder.setNo(no);
+        saleOrder.setCustomerId(customerId);
+        saleOrder.setContractNo(head.getContractNo());
+        saleOrder.setOrderTime(parseImportOrderTime(head.getOrderTime()));
+        saleOrder.setRemark(head.getRemark());
+        saleOrder.setStatus(ErpAuditStatus.DRAFT.getStatus());
+        saleOrder.setDepositPrice(BigDecimal.ZERO);
+        saleOrder.setOutCount(BigDecimal.ZERO);
+        saleOrder.setOutStatus(ErpSaleOrderOutStatusEnum.NOT_OUT.getStatus());
+
+        List<ErpSaleOrderItemDO> itemDOs = validateSaleOrderItems(items);
+        calculateTotalPrice(saleOrder, itemDOs);
+        saleOrderMapper.insert(saleOrder);
+        itemDOs.forEach(item -> item.setOrderId(saleOrder.getId()));
+        saleOrderItemMapper.insertBatch(itemDOs);
+        return saleOrder;
+    }
+
+    /**
+     * 瑙f瀽鈥滄槸鍚﹂渶瑕佺敓浜р�濓細鐣欑┖榛樿鈥滄槸(1)鈥濓紝濉�滃惁/0/涓嶉渶瑕佲�濆垯涓衡�滃惁(0)鈥�
+     */
+    private Integer parseNeedProduction(String text) {
+        if (StrUtil.isBlank(text)) {
+            return 1; // 涓庨〉闈㈡柊澧炴槑缁嗙殑榛樿鍊间竴鑷�
+        }
+        String value = text.trim();
+        boolean no = "鍚�".equals(value) || "0".equals(value) || "涓嶉渶瑕�".equals(value)
+                || "false".equalsIgnoreCase(value) || "no".equalsIgnoreCase(value);
+        return no ? 0 : 1;
+    }
+
+    /**
+     * 瑙f瀽瀵煎叆鐨勪笅鍗曟椂闂达紝鍏煎澶氱甯歌鏍煎紡锛涗负绌哄彇褰撳墠鏃堕棿
+     */
+    private LocalDateTime parseImportOrderTime(String text) {
+        if (StrUtil.isBlank(text)) {
+            return LocalDateTime.now();
+        }
+        String value = text.trim();
+        for (String pattern : new String[]{"yyyy-MM-dd HH:mm:ss", "yyyy-MM-dd HH:mm", "yyyy/MM/dd HH:mm:ss"}) {
+            try {
+                return LocalDateTime.parse(value, DateTimeFormatter.ofPattern(pattern));
+            } catch (Exception ignored) {
+                // 缁х画灏濊瘯涓嬩竴涓牸寮�
+            }
+        }
+        for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+            try {
+                return LocalDate.parse(value, DateTimeFormatter.ofPattern(pattern)).atStartOfDay();
+            } catch (Exception ignored) {
+                // 缁х画灏濊瘯涓嬩竴涓牸寮�
+            }
+        }
+        throw new IllegalArgumentException("涓嬪崟鏃堕棿鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01 10:00:00");
+    }
+
+    /**
+     * 鎻愬彇瀵煎叆澶辫触鍘熷洜锛圫erviceException 鍙栦笟鍔℃彁绀猴級
+     */
+    private String resolveImportErrorMessage(Exception ex) {
+        if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) {
+            return serviceException.getMessage();
+        }
+        return StrUtil.blankToDefault(ex.getMessage(), "瀵煎叆澶辫触");
+    }
+
     private void calculateTotalPrice(ErpSaleOrderDO saleOrder, List<ErpSaleOrderItemDO> saleOrderItems) {
         saleOrder.setTotalCount(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getCount, BigDecimal::add));
         saleOrder.setTotalProductPrice(getSumValue(saleOrderItems, ErpSaleOrderItemDO::getTotalPrice, BigDecimal::add, BigDecimal.ZERO));
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
index 531ccb3..259ea74 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/MesDvMeteringController.java
@@ -11,6 +11,8 @@
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
 import cn.iocoder.yudao.module.mes.enums.dv.MesDvMeteringExpireStatusEnum;
 import cn.iocoder.yudao.module.mes.service.dv.metering.MesDvMeteringService;
@@ -27,10 +29,12 @@
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
+import org.springframework.web.multipart.MultipartFile;
 
 import java.io.IOException;
 import java.time.LocalDate;
 import java.time.temporal.ChronoUnit;
+import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.Map;
@@ -108,6 +112,29 @@
                 buildMeteringRespVOList(list));
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱璁¢噺鍣ㄥ叿瀵煎叆妯℃澘")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:import')")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<MesDvMeteringImportExcelVO> list = Collections.singletonList(
+                MesDvMeteringImportExcelVO.builder()
+                        .code("JL-001").name("绀轰緥娓告爣鍗″昂").category(1).specification("0-150mm")
+                        .brand("绀轰緥鍝佺墝").precision("0.02mm").range("0-150mm")
+                        .deptName("鐮斿彂閮ㄩ棬").chargeUserName("瓒呯骇绠$悊鍛�")
+                        .checkCycleCount(12).useDate("2026-01-01").status(1).remark("绀轰緥鏁版嵁锛屽鍏ュ墠璇锋浛鎹�")
+                        .build());
+        ExcelUtils.write(response, "璁¢噺鍣ㄥ叿瀵煎叆妯℃澘.xls", "璁¢噺鍣ㄥ叿", MesDvMeteringImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆璁¢噺鍣ㄥ叿")
+    @PreAuthorize("@ss.hasPermission('mes:dv-metering:import')")
+    public CommonResult<MesDvMeteringImportRespVO> importMeteringExcel(
+            @RequestParam("file") MultipartFile file) throws Exception {
+        List<MesDvMeteringImportExcelVO> list = ExcelUtils.read(file, MesDvMeteringImportExcelVO.class);
+        return success(meteringService.importMeteringList(list));
+    }
+
     @GetMapping("/simple-list")
     @Operation(summary = "鑾峰緱璁¢噺鍣ㄥ叿绮剧畝鍒楄〃", description = "涓昏鐢ㄤ簬鍓嶇鐨勪笅鎷夐�夐」")
     @PreAuthorize("@ss.hasPermission('mes:dv-metering:query')")
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportExcelVO.java
new file mode 100644
index 0000000..4b7e797
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportExcelVO.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import cn.iocoder.yudao.framework.excel.core.annotations.DictFormat;
+import cn.iocoder.yudao.framework.excel.core.convert.DictConvert;
+import cn.iocoder.yudao.module.mes.enums.DictTypeConstants;
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * 绠$悊鍚庡彴 - 璁¢噺鍣ㄥ叿瀵煎叆 Excel VO
+ *
+ * 璇存槑锛氫笌瀵煎嚭鍒椾繚鎸佷竴鑷达紝鍓旈櫎绯荤粺鍒楋紙缂栧彿銆佷笂娆�/涓嬫妫�娴嬫棩鏈熴�佸埌鏈熺姸鎬併�佸墿浣欏ぉ鏁般�佸垱寤烘椂闂达級銆�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 璁¢噺鍣ㄥ叿瀵煎叆 Excel VO")
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MesDvMeteringImportExcelVO {
+
+    @ExcelProperty("璁¢噺鍣ㄥ叿缂栫爜")
+    @Schema(description = "璁¢噺鍣ㄥ叿缂栫爜锛屽敮涓�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String code;
+
+    @ExcelProperty("璁¢噺鍣ㄥ叿鍚嶇О")
+    @Schema(description = "璁¢噺鍣ㄥ叿鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED)
+    private String name;
+
+    @ExcelProperty(value = "鍣ㄥ叿绫诲埆", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_DV_METERING_CATEGORY)
+    @Schema(description = "鍣ㄥ叿绫诲埆锛堝~瀛楀吀涓枃锛屽 鍗″昂/鍏朵粬锛�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Integer category;
+
+    @ExcelProperty("瑙勬牸鍨嬪彿")
+    @Schema(description = "瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @ExcelProperty("鍝佺墝/鐢熶骇鍘傚")
+    @Schema(description = "鍝佺墝/鐢熶骇鍘傚")
+    private String brand;
+
+    @ExcelProperty("绮惧害")
+    @Schema(description = "绮惧害")
+    private String precision;
+
+    @ExcelProperty("閲忕▼")
+    @Schema(description = "閲忕▼")
+    private String range;
+
+    @ExcelProperty("浣跨敤閮ㄩ棬")
+    @Schema(description = "浣跨敤閮ㄩ棬鍚嶇О锛堥渶涓庣郴缁熼儴闂ㄥ悕绉颁竴鑷达級")
+    private String deptName;
+
+    @ExcelProperty("璐d换浜�")
+    @Schema(description = "璐d换浜烘樀绉帮紙闇�涓庣郴缁熺敤鎴锋樀绉颁竴鑷达級")
+    private String chargeUserName;
+
+    @ExcelProperty("鏍″噯鍛ㄦ湡(鏈�)")
+    @Schema(description = "鏍″噯鍛ㄦ湡锛堟湀锛夛紱鐣欑┖榛樿 12", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Integer checkCycleCount;
+
+    @ExcelProperty("鎶曞叆浣跨敤鏃ユ湡")
+    @Schema(description = "鎶曞叆浣跨敤鏃ユ湡锛屾牸寮� yyyy-MM-dd")
+    private String useDate;
+
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.MES_DV_METERING_STATUS)
+    @Schema(description = "鐘舵�侊紙濉瓧鍏镐腑鏂囷紝濡� 鍦ㄧ敤/鍋滅敤/鎶ュ簾锛�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Integer status;
+
+    @ExcelProperty("澶囨敞")
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportRespVO.java
new file mode 100644
index 0000000..2ddfdab
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/dv/metering/vo/MesDvMeteringImportRespVO.java
@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 绠$悊鍚庡彴 - 璁¢噺鍣ㄥ叿瀵煎叆 Response VO
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Schema(description = "绠$悊鍚庡彴 - 璁¢噺鍣ㄥ叿瀵煎叆 Response VO")
+@Data
+@Builder
+public class MesDvMeteringImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勮閲忓櫒鍏风紪鐮佹暟缁�", example = "JL-001")
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勮閲忓櫒鍏风紪鐮佹暟缁�")
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触闆嗗悎锛宬ey 涓鸿閲忓櫒鍏风紪鐮佹垨琛屽彿锛寁alue 涓哄け璐ュ師鍥�")
+    private Map<String, String> failureList;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java
index 1583a20..4f79347 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java
@@ -1,11 +1,15 @@
 package cn.iocoder.yudao.module.mes.dal.mysql.dv.metering;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import com.baomidou.mybatisplus.annotation.InterceptorIgnore;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
 import org.apache.ibatis.annotations.Mapper;
+import org.apache.ibatis.annotations.Param;
+import org.apache.ibatis.annotations.Select;
+import org.apache.ibatis.annotations.Update;
 
 import java.time.LocalDate;
 import java.util.List;
@@ -36,6 +40,20 @@
         return selectOne(MesDvMeteringDO::getCode, code);
     }
 
+    /**
+     * 鎸夌紪鐮佹煡璇紙鍚�滃凡琚�昏緫鍒犻櫎鈥濈殑璁板綍锛夆�斺�旂敤浜庡鍏ユ椂鍒ら噸
+     */
+    @InterceptorIgnore(tenantLine = "true", dataPermission = "true")
+    @Select("SELECT * FROM mes_dv_metering WHERE code = #{code} ORDER BY id DESC LIMIT 1")
+    MesDvMeteringDO selectByCodeIncludeDeleted(@Param("code") String code);
+
+    /**
+     * 鎭㈠琚�昏緫鍒犻櫎鐨勮褰曪紙鎶� deleted 缃洖 0锛�
+     */
+    @InterceptorIgnore(tenantLine = "true", dataPermission = "true")
+    @Update("UPDATE mes_dv_metering SET deleted = 0 WHERE id = #{id}")
+    int restoreDeletedById(@Param("id") Long id);
+
     default List<MesDvMeteringDO> selectListByNextCheckDateBetween(LocalDate begin, LocalDate end) {
         return selectList(new LambdaQueryWrapperX<MesDvMeteringDO>()
                 .ge(MesDvMeteringDO::getNextCheckDate, begin)
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java
index 6104c6e..217d1db 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringService.java
@@ -3,6 +3,8 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportRespVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.dv.metering.MesDvMeteringDO;
 import jakarta.validation.Valid;
 
@@ -29,6 +31,14 @@
     Long createMetering(@Valid MesDvMeteringSaveReqVO createReqVO);
 
     /**
+     * 瀵煎叆璁¢噺鍣ㄥ叿锛堝鍏ュ悗涓烘甯告暟鎹紝缂栫爜宸插瓨鍦ㄥ垯璁板叆澶辫触鍒楄〃锛�
+     *
+     * @param importList 瀵煎叆鏁版嵁
+     * @return 瀵煎叆缁撴灉
+     */
+    MesDvMeteringImportRespVO importMeteringList(List<MesDvMeteringImportExcelVO> importList);
+
+    /**
      * 鏇存柊璁¢噺鍣ㄥ叿
      *
      * @param updateReqVO 鏇存柊淇℃伅
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
index 6c5ccbe..d7e0633 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringServiceImpl.java
@@ -1,6 +1,12 @@
 package cn.iocoder.yudao.module.mes.service.dv.metering;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.dv.metering.vo.MesDvMeteringImportRespVO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
+import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import cn.hutool.core.util.ObjUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
@@ -14,9 +20,14 @@
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
 import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.LinkedHashMap;
+import java.util.Map;
 import java.util.Collection;
 import java.util.Collections;
 import java.util.List;
@@ -54,6 +65,11 @@
     @Resource
     private ConfigApi configApi;
 
+    @Resource
+    private DeptApi deptApi;
+    @Resource
+    private AdminUserApi adminUserApi;
+
     @Override
     public Long createMetering(MesDvMeteringSaveReqVO createReqVO) {
         // 鏍¢獙缂栫爜鍞竴
@@ -66,6 +82,163 @@
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MesDvMeteringImportRespVO importMeteringList(List<MesDvMeteringImportExcelVO> importList) {
+        MesDvMeteringImportRespVO respVO = MesDvMeteringImportRespVO.builder()
+                .createList(new ArrayList<>())
+                .updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>())
+                .build();
+        if (CollUtil.isEmpty(importList)) {
+            return respVO;
+        }
+
+        // 1. 棰勭儹鈥滈儴闂ㄥ悕绉� -> 閮ㄩ棬缂栧彿鈥濇槧灏勶紙閮ㄩ棬/璐d换浜轰负闈炲繀濉紝鍖归厤涓嶅埌璇ヨ澶辫触锛�
+        Map<String, Long> deptIdMap = new HashMap<>();
+        List<DeptRespDTO> depts = deptApi.getDeptList();
+        if (CollUtil.isNotEmpty(depts)) {
+            depts.forEach(dept -> {
+                if (StrUtil.isNotBlank(dept.getName())) {
+                    deptIdMap.putIfAbsent(dept.getName().trim(), dept.getId());
+                }
+            });
+        }
+
+        // 2. 閫愯瀵煎叆锛堝崟琛屽け璐ヤ笉褰卞搷鍏跺畠琛岋級
+        for (int i = 0; i < importList.size(); i++) {
+            MesDvMeteringImportExcelVO row = importList.get(i);
+            if (row == null || (StrUtil.isBlank(row.getCode()) && StrUtil.isBlank(row.getName()))) {
+                continue; // 璺宠繃绌鸿
+            }
+            String label = StrUtil.isNotBlank(row.getCode()) ? row.getCode().trim() : "绗� " + (i + 2) + " 琛�";
+            try {
+                boolean restored = createMeteringByImport(row, deptIdMap);
+                if (restored) {
+                    respVO.getUpdateList().add(row.getCode().trim()); // 鎭㈠/瑕嗙洊鏇存柊
+                } else {
+                    respVO.getCreateList().add(row.getCode().trim());
+                }
+            } catch (Exception ex) {
+                respVO.getFailureList().put(label, resolveImportErrorMessage(ex));
+            }
+        }
+        return respVO;
+    }
+
+    /**
+     * 渚濇嵁瀵煎叆琛屽垱寤轰竴鏉¤閲忓櫒鍏疯褰�
+     */
+    private boolean createMeteringByImport(MesDvMeteringImportExcelVO row, Map<String, Long> deptIdMap) {
+        // 1. 蹇呭~鏍¢獙
+        if (StrUtil.isBlank(row.getCode())) {
+            throw new IllegalArgumentException("璁¢噺鍣ㄥ叿缂栫爜涓嶈兘涓虹┖");
+        }
+        if (StrUtil.isBlank(row.getName())) {
+            throw new IllegalArgumentException("璁¢噺鍣ㄥ叿鍚嶇О涓嶈兘涓虹┖");
+        }
+        if (row.getCategory() == null) {
+            throw new IllegalArgumentException("鍣ㄥ叿绫诲埆涓嶈兘涓虹┖锛堣濉瓧鍏镐腑鏂囷紝濡� 鍗″昂/鍏朵粬锛�");
+        }
+        if (row.getStatus() == null) {
+            throw new IllegalArgumentException("鐘舵�佷笉鑳戒负绌猴紙璇峰~瀛楀吀涓枃锛屽 鍦ㄧ敤/鍋滅敤/鎶ュ簾锛�");
+        }
+        // 2. 缂栫爜鍒ら噸锛堝惈鈥滃凡琚�昏緫鍒犻櫎鈥濈殑璁板綍锛夛細鏈垹闄� => 鎶ラ敊锛涘凡鍒犻櫎 => 绋嶅悗鎭㈠骞舵洿鏂�
+        MesDvMeteringDO exist = meteringMapper.selectByCodeIncludeDeleted(row.getCode().trim());
+        if (exist != null && !Boolean.TRUE.equals(exist.getDeleted())) {
+            throw new IllegalArgumentException("璁¢噺鍣ㄥ叿缂栫爜宸插瓨鍦細" + row.getCode());
+        }
+
+        // 3. 缁勮
+        MesDvMeteringSaveReqVO reqVO = new MesDvMeteringSaveReqVO();
+        reqVO.setCode(row.getCode().trim());
+        reqVO.setName(row.getName().trim());
+        reqVO.setCategory(row.getCategory());
+        reqVO.setSpecification(row.getSpecification());
+        reqVO.setBrand(row.getBrand());
+        reqVO.setPrecision(row.getPrecision());
+        reqVO.setRange(row.getRange());
+        reqVO.setCheckCycleCount(row.getCheckCycleCount() == null ? 12 : row.getCheckCycleCount());
+        reqVO.setUseDate(parseImportUseDate(row.getUseDate()));
+        reqVO.setStatus(row.getStatus());
+        reqVO.setRemark(row.getRemark());
+        // 浣跨敤閮ㄩ棬锛氭寜鍚嶇О鍖归厤
+        if (StrUtil.isNotBlank(row.getDeptName())) {
+            Long deptId = deptIdMap.get(row.getDeptName().trim());
+            if (deptId == null) {
+                throw new IllegalArgumentException("浣跨敤閮ㄩ棬涓嶅瓨鍦細" + row.getDeptName());
+            }
+            reqVO.setDeptId(deptId);
+        }
+        // 璐d换浜猴細鎸夋樀绉板尮閰�
+        if (StrUtil.isNotBlank(row.getChargeUserName())) {
+            Long userId = matchUserIdByNickname(row.getChargeUserName().trim());
+            if (userId == null) {
+                throw new IllegalArgumentException("璐d换浜轰笉瀛樺湪锛�" + row.getChargeUserName());
+            }
+            reqVO.setChargeUserId(userId);
+        }
+
+        // 4. 钀藉簱锛氬懡涓�滃凡鍒犻櫎鈥濈殑鍚岀紪鐮� => 鍏堟仮澶嶅啀鏇存柊锛涘惁鍒欐柊澧�
+        if (exist != null) {
+            meteringMapper.restoreDeletedById(exist.getId());
+            reqVO.setId(exist.getId());
+            updateMetering(reqVO);
+            return true; // 琛ㄧず鈥滄仮澶�/鏇存柊鈥�
+        }
+        createMetering(reqVO);
+        return false;
+    }
+
+    /**
+     * 鎸夋樀绉板尮閰嶇敤鎴风紪鍙凤紙绮剧‘鍖归厤浼樺厛锛屽彇涓嶅埌鍒欑敤妯$硦缁撴灉绗竴鏉★級
+     */
+    private Long matchUserIdByNickname(String nickname) {
+        List<AdminUserRespDTO> users = adminUserApi.getUserListByNickname(nickname);
+        if (CollUtil.isEmpty(users)) {
+            return null;
+        }
+        return users.stream()
+                .filter(user -> nickname.equals(user.getNickname()))
+                .map(AdminUserRespDTO::getId)
+                .findFirst()
+                .orElseGet(() -> users.get(0).getId());
+    }
+
+    /**
+     * 瑙f瀽鎶曞叆浣跨敤鏃ユ湡锛坹yyy-MM-dd / yyyy/M/d锛夛紱涓虹┖杩斿洖 null
+     */
+    private LocalDate parseImportUseDate(String text) {
+        if (StrUtil.isBlank(text)) {
+            return null;
+        }
+        String value = text.trim();
+        for (String pattern : new String[]{"yyyy-MM-dd", "yyyy/M/d"}) {
+            try {
+                return LocalDate.parse(value, java.time.format.DateTimeFormatter.ofPattern(pattern));
+            } catch (Exception ignored) {
+                // 缁х画灏濊瘯涓嬩竴涓牸寮�
+            }
+        }
+        try {
+            return java.time.LocalDateTime.parse(value,
+                    java.time.format.DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")).toLocalDate();
+        } catch (Exception ignored) {
+            // 蹇界暐
+        }
+        throw new IllegalArgumentException("鎶曞叆浣跨敤鏃ユ湡鏍煎紡涓嶆纭細" + text + "锛岀ず渚� 2026-01-01");
+    }
+
+    /**
+     * 鎻愬彇瀵煎叆澶辫触鍘熷洜锛圫erviceException 鍙栦笟鍔℃彁绀猴級
+     */
+    private String resolveImportErrorMessage(Exception ex) {
+        if (ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException serviceException) {
+            return serviceException.getMessage();
+        }
+        return StrUtil.blankToDefault(ex.getMessage(), "瀵煎叆澶辫触");
+    }
+
+    @Override
     public void updateMetering(MesDvMeteringSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
         validateMeteringExists(updateReqVO.getId());

--
Gitblit v1.9.3