From 44840942aba889088a86d2b3d93856530bb2dc25 Mon Sep 17 00:00:00 2001
From: hsy <1029150275@qq.com>
Date: 星期二, 25 八月 2026 16:51:29 +0800
Subject: [PATCH] Merge branch 'dev_business' into fix/hsy/20260811

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java                         |   79 +
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java                       |   31 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java                   |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmOvertimeApplicationDO.java                     |   46 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java                     |    8 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java                             |   19 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java                         |   42 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java                              |   10 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java            |  102 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java                        |    6 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigRespVO.java    |   33 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java          |    3 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java                 |   78 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/mysql/dv/metering/MesDvMeteringMapper.java                            |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java                             |   10 
 yudao-server/src/main/resources/application-wtxc.yaml                                                                                |  222 ++++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmOvertimeApplicationMapper.java                      |   38 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java                                             |    2 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java                                   |   11 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java                           |    6 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java                               |   72 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java                    |   80 +
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java                   |   30 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java                                          |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportExcelVO.java          |   39 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigService.java               |   28 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/companyconfig/SystemCompanyConfigDO.java             |   47 
 sql/mysql/hrm_module_init.sql                                                                                                        |    6 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderServiceImpl.java                         |   18 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java                                             |    1 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java                        |   86 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmOvertimeApplicationController.java           |   71 +
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java                            |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationSaveReqVO.java         |   36 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java                  |   30 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/ErpPurchaseOrderService.java                             |    8 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java                                              |    7 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java                                                 |    3 
 sql/mysql/hrm_salary_structure_overtime_ratio.sql                                                                                    |    8 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java     |    4 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java                                             |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigCreateReqVO.java             |   32 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java                                          |   10 
 yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java                       |   10 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java                 |   20 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java                  |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationPageReqVO.java         |   18 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java             |    4 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmOvertimeApplicationStatusListener.java       |   20 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java                              |   29 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java                             |   76 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java                           |   14 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemTypeController.java                     |   31 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java                            |   13 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java                                      |  128 ++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java                             |    6 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportExcelVO.java                 |   45 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java                                 |   11 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java       |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java                         |   16 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java                                 |   31 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigServiceImpl.java           |   50 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportExcelVO.java      |   51 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java                 |   25 
 sql/mysql/hrm_overtime_application.sql                                                                                               |   64 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java                                  |    6 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java                              |    5 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmOvertimeTypeEnum.java                                        |   31 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportRespVO.java           |   24 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java                               |    1 
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java                               |   16 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java         |   14 
 docs/projectSql/ruoyi-vue-pro.sql                                                                                                    |    4 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java             |   54 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java                 |   81 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java                   |  263 +++++
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java                 |   16 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java                       |   99 +
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigSaveReqVO.java |   35 
 yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/sale/ErpSaleOrderServiceImpl.java                                 |   13 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationRespVO.java            |   41 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java               |   51 
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                         |   12 
 yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java                                |   49 
 yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java                    |   14 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationService.java                       |   26 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportReqVO.java        |   18 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportRespVO.java                  |   24 
 yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportRespVO.java                         |   24 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/companyconfig/SystemCompanyConfigMapper.java              |   15 
 sql/mysql/hrm_salary_structure_menu.sql                                                                                              |   34 
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/SystemCompanyConfigController.java   |   59 +
 yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java      |    3 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java                                 |   14 
 94 files changed, 3,007 insertions(+), 99 deletions(-)

diff --git a/docs/projectSql/ruoyi-vue-pro.sql b/docs/projectSql/ruoyi-vue-pro.sql
index 12fd82f..1a34cf4 100644
--- a/docs/projectSql/ruoyi-vue-pro.sql
+++ b/docs/projectSql/ruoyi-vue-pro.sql
@@ -4903,7 +4903,9 @@
   `name` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NOT NULL COMMENT '缁撴瀯鍚嶇О',
   `base_salary` decimal(12, 2) NOT NULL COMMENT '鍩烘湰宸ヨ祫',
   `performance_ratio` decimal(4, 2) NULL DEFAULT 0.00 COMMENT '缁╂晥宸ヨ祫鍗犳瘮锛�%锛�',
-  `overtime_pay_ratio` decimal(4, 2) NULL DEFAULT 1.50 COMMENT '鍔犵彮宸ヨ祫鍊嶇巼',
+  `overtime_pay_ratio` decimal(4, 2) NULL DEFAULT 1.50 COMMENT '宸ヤ綔鏃ュ姞鐝伐璧勫�嶇巼',
+  `rest_day_overtime_pay_ratio` decimal(4, 2) NULL DEFAULT 2.00 COMMENT '浼戞伅鏃ュ姞鐝伐璧勫�嶇巼',
+  `legal_holiday_overtime_pay_ratio` decimal(4, 2) NULL DEFAULT 3.00 COMMENT '娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼',
   `social_security_base` decimal(12, 2) NULL DEFAULT NULL COMMENT '绀句繚鍩烘暟',
   `housing_fund_base` decimal(12, 2) NULL DEFAULT NULL COMMENT '鍏Н閲戝熀鏁�',
   `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
diff --git a/sql/mysql/hrm_module_init.sql b/sql/mysql/hrm_module_init.sql
index 57c4d35..d1d46a3 100644
--- a/sql/mysql/hrm_module_init.sql
+++ b/sql/mysql/hrm_module_init.sql
@@ -274,7 +274,9 @@
     `name` varchar(100) NOT NULL COMMENT '缁撴瀯鍚嶇О',
     `base_salary` decimal(12,2) NOT NULL COMMENT '鍩烘湰宸ヨ祫',
     `performance_ratio` decimal(4,2) DEFAULT 0 COMMENT '缁╂晥宸ヨ祫鍗犳瘮锛�%锛�',
-    `overtime_pay_ratio` decimal(4,2) DEFAULT 1.5 COMMENT '鍔犵彮宸ヨ祫鍊嶇巼',
+    `overtime_pay_ratio` decimal(4,2) DEFAULT 1.5 COMMENT '宸ヤ綔鏃ュ姞鐝伐璧勫�嶇巼',
+    `rest_day_overtime_pay_ratio` decimal(4,2) DEFAULT 2.0 COMMENT '浼戞伅鏃ュ姞鐝伐璧勫�嶇巼',
+    `legal_holiday_overtime_pay_ratio` decimal(4,2) DEFAULT 3.0 COMMENT '娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼',
     `social_security_base` decimal(12,2) DEFAULT NULL COMMENT '绀句繚鍩烘暟',
     `housing_fund_base` decimal(12,2) DEFAULT NULL COMMENT '鍏Н閲戝熀鏁�',
     `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
@@ -586,4 +588,4 @@
 -- =============================================
 -- 鎵ц瀹屾垚鎻愮ず
 -- =============================================
-SELECT 'HRM 浜哄姏璧勬簮妯″潡 SQL 鍒濆鍖栧畬鎴愶紒' AS result;
\ No newline at end of file
+SELECT 'HRM 浜哄姏璧勬簮妯″潡 SQL 鍒濆鍖栧畬鎴愶紒' AS result;
diff --git a/sql/mysql/hrm_overtime_application.sql b/sql/mysql/hrm_overtime_application.sql
new file mode 100644
index 0000000..7983a9b
--- /dev/null
+++ b/sql/mysql/hrm_overtime_application.sql
@@ -0,0 +1,64 @@
+-- HRM 鍔犵彮鐢宠锛氬崗鍚屽姙鍏鎵� + 钖叕鏍哥畻鏁版嵁婧�
+
+CREATE TABLE IF NOT EXISTS `hrm_overtime_application` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '鐢宠ID',
+    `no` varchar(32) NOT NULL COMMENT '鐢宠鍗曞彿',
+    `user_id` bigint NOT NULL COMMENT '绯荤粺鐢ㄦ埛ID',
+    `employee_id` bigint NOT NULL COMMENT '鍛樺伐ID',
+    `dept_id` bigint NOT NULL COMMENT '閮ㄩ棬ID',
+    `overtime_date` date NOT NULL COMMENT '鍔犵彮鏃ユ湡',
+    `start_time` time NOT NULL COMMENT '寮�濮嬫椂闂�',
+    `end_time` time NOT NULL COMMENT '缁撴潫鏃堕棿',
+    `duration` decimal(6,1) NOT NULL COMMENT '鎵i櫎鐢ㄩ鍚庣殑鍔犵彮鏃堕暱锛�0.5灏忔椂鍙栨暣锛�',
+    `overtime_type` tinyint NOT NULL COMMENT '1-宸ヤ綔鏃ワ紝2-浼戞伅鏃ワ紝3-娉曞畾鑺傚亣鏃�',
+    `cross_day` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁璺ㄥぉ',
+    `meal_deduction` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鎵i櫎鐢ㄩ鏃堕棿',
+    `meal_deduction_hours` decimal(4,1) NOT NULL DEFAULT 0 COMMENT '鐢ㄩ鎵i櫎灏忔椂鏁�',
+    `reason` varchar(500) NOT NULL COMMENT '鍔犵彮鍘熷洜',
+    `related_type` tinyint DEFAULT NULL COMMENT '鍏宠仈绫诲瀷锛�1-椤圭洰锛�2-宸ュ崟',
+    `related_code` varchar(100) DEFAULT NULL COMMENT '椤圭洰/宸ュ崟缂栧彿',
+    `work_content` varchar(1000) DEFAULT NULL COMMENT '宸ヤ綔鍐呭绠�杩�',
+    `status` tinyint NOT NULL DEFAULT 0 COMMENT '0-鑽夌锛�10-瀹℃壒涓紝20-閫氳繃锛�30-椹冲洖锛�40-鎾ら攢',
+    `process_instance_id` varchar(64) DEFAULT NULL COMMENT '鍗忓悓鍔炲叕娴佺▼瀹炰緥ID',
+    `approve_time` datetime DEFAULT NULL COMMENT '瀹℃壒瀹屾垚鏃堕棿',
+    `creator` varchar(64) DEFAULT '' COMMENT '鍒涘缓鑰�',
+    `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+    `updater` varchar(64) DEFAULT '' COMMENT '鏇存柊鑰�',
+    `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '鏇存柊鏃堕棿',
+    `deleted` bit(1) NOT NULL DEFAULT b'0' COMMENT '鏄惁鍒犻櫎',
+    PRIMARY KEY (`id`),
+    UNIQUE KEY `uk_hrm_overtime_no` (`no`),
+    KEY `idx_hrm_overtime_user_date_status` (`user_id`, `overtime_date`, `status`),
+    KEY `idx_hrm_overtime_process_instance` (`process_instance_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM鍔犵彮鐢宠琛�';
+
+-- 鍗忓悓鍔炲叕娴佺▼鍒嗙被宸插湪 bpm/manager/category 涓淮鎶わ紝鍒嗙被缂栫爜涓� overtime_approve銆�
+-- 浠嶉渶鍦ㄨ鍒嗙被涓嬪缓绔嬪苟鍙戝竷娴佺▼瀹氫箟 key=hrm_overtime銆�
+
+-- 浜轰簨瀹℃壒鑿滃崟鍙婃寜閽潈闄�
+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 6007, '鍔犵彮鐢宠', '', 2, 4, 6002, 'overtime', 'ep:timer', 'hrm/overtime/index', 'HrmOvertimeApplication', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'
+FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 6007);
+
+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 60071, '鍔犵彮鐢宠鏌ヨ', 'hrm:overtime:query', 3, 1, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60071);
+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 60072, '鍔犵彮鐢宠鏂板', 'hrm:overtime:create', 3, 2, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60072);
+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 60073, '鍔犵彮鐢宠淇敼', 'hrm:overtime:update', 3, 3, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60073);
+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 60074, '鍔犵彮鐢宠鍒犻櫎', 'hrm:overtime:delete', 3, 4, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60074);
+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 60075, '鍔犵彮鐢宠鎻愪氦', 'hrm:overtime:submit', 3, 5, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60075);
+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 60076, '鍔犵彮鐢宠鎾ら攢', 'hrm:overtime:cancel', 3, 6, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60076);
+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 60077, '鍔犵彮鐢宠绠$悊', 'hrm:overtime:manage', 3, 7, 6007, '', '', '', NULL, 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0' FROM DUAL WHERE NOT EXISTS (SELECT 1 FROM `system_menu` WHERE `id` = 60077);
+
+INSERT INTO `system_role_menu` (`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1, menu_id, '1', NOW(), '1', NOW(), b'0' FROM (
+    SELECT 6007 menu_id UNION ALL SELECT 60071 UNION ALL SELECT 60072 UNION ALL
+    SELECT 60073 UNION ALL SELECT 60074 UNION ALL SELECT 60075 UNION ALL SELECT 60076 UNION ALL SELECT 60077
+) ids WHERE NOT EXISTS (SELECT 1 FROM `system_role_menu` rm WHERE rm.`role_id` = 1 AND rm.`menu_id` = ids.menu_id);
+
+SELECT 'HRM 鍔犵彮鐢宠鍒濆鍖栧畬鎴愶紱璇峰湪鍗忓悓鍔炲叕鍙戝竷 hrm_overtime 娴佺▼' AS result;
diff --git a/sql/mysql/hrm_salary_structure_menu.sql b/sql/mysql/hrm_salary_structure_menu.sql
new file mode 100644
index 0000000..5e03ee4
--- /dev/null
+++ b/sql/mysql/hrm_salary_structure_menu.sql
@@ -0,0 +1,34 @@
+-- HRM 钖叕缁撴瀯閰嶇疆鑿滃崟涓庢潈闄愶紙鍙噸澶嶆墽琛岋級
+
+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
+(6025, '钖叕缁撴瀯', '', 2, 3, 6020, 'structure', 'ep:set-up',
+ 'hrm/salary/structure/index', 'HrmSalaryStructure', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60251, '钖叕缁撴瀯鏌ヨ', 'hrm:salary-structure:query', 3, 1, 6025, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60252, '钖叕缁撴瀯鏂板', 'hrm:salary-structure:create', 3, 2, 6025, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60253, '钖叕缁撴瀯淇敼', 'hrm:salary-structure:update', 3, 3, 6025, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60254, '钖叕缁撴瀯鍒犻櫎', 'hrm:salary-structure:delete', 3, 4, 6025, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0')
+ON DUPLICATE KEY UPDATE
+`name` = VALUES(`name`), `permission` = VALUES(`permission`), `parent_id` = VALUES(`parent_id`),
+`path` = VALUES(`path`), `icon` = VALUES(`icon`), `component` = VALUES(`component`),
+`component_name` = VALUES(`component_name`), `status` = VALUES(`status`), `deleted` = b'0';
+
+INSERT INTO `system_role_menu`
+(`role_id`, `menu_id`, `creator`, `create_time`, `updater`, `update_time`, `deleted`)
+SELECT 1, menu_id, '1', NOW(), '1', NOW(), b'0'
+FROM (
+    SELECT 6025 AS menu_id UNION ALL SELECT 60251 UNION ALL SELECT 60252
+    UNION ALL SELECT 60253 UNION ALL SELECT 60254
+) menu_ids
+WHERE NOT EXISTS (
+    SELECT 1 FROM `system_role_menu` role_menu
+    WHERE role_menu.`role_id` = 1 AND role_menu.`menu_id` = menu_ids.menu_id AND role_menu.`deleted` = b'0'
+);
+
+SELECT 'HRM 钖叕缁撴瀯鑿滃崟鍒濆鍖栧畬鎴�' AS result;
diff --git a/sql/mysql/hrm_salary_structure_overtime_ratio.sql b/sql/mysql/hrm_salary_structure_overtime_ratio.sql
new file mode 100644
index 0000000..25e7fe4
--- /dev/null
+++ b/sql/mysql/hrm_salary_structure_overtime_ratio.sql
@@ -0,0 +1,8 @@
+-- 钖叕缁撴瀯澧炲姞浼戞伅鏃ャ�佹硶瀹氳妭鍋囨棩鍔犵彮宸ヨ祫鍊嶇巼
+-- 宸叉湁鐨勫伐浣滄棩鍊嶇巼缁х画浣跨敤 overtime_pay_ratio 瀛楁
+
+ALTER TABLE `hrm_salary_structure`
+    ADD COLUMN `rest_day_overtime_pay_ratio` decimal(4,2) DEFAULT 2.00
+        COMMENT '浼戞伅鏃ュ姞鐝伐璧勫�嶇巼' AFTER `overtime_pay_ratio`,
+    ADD COLUMN `legal_holiday_overtime_pay_ratio` decimal(4,2) DEFAULT 3.00
+        COMMENT '娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼' AFTER `rest_day_overtime_pay_ratio`;
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
index 3242dec..6dc6ae3 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtils.java
@@ -27,6 +27,7 @@
 import org.flowable.task.api.TaskInfo;
 import tools.jackson.databind.JsonNode;
 
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.LinkedHashMap;
 import java.util.List;
@@ -245,6 +246,11 @@
                 || !BpmModelFormTypeEnum.NORMAL.getType().equals(processDefinitionInfo.getFormType())) {
             return null;
         }
+        // 鍘嗗彶娴佺▼瀹氫箟銆佷笟鍔¤〃鍗曟祦绋嬪彲鑳芥病鏈夊啑浣� formFields銆傛鏃舵病鏈夊彲灞曠ず鐨勬憳瑕侊紝
+        // 涓嶈兘鍥犱负鍗曟潯娴佺▼鏁版嵁缂哄皯琛ㄥ崟瀛楁閰嶇疆瀵艰嚧鏁翠釜鈥滄垜鐨勬祦绋�/寰呭姙鈥濆垎椤垫煡璇㈠け璐ャ��
+        if (ObjectUtil.isEmpty(processDefinitionInfo.getFormFields())) {
+            return Collections.emptyList();
+        }
 
         // 瑙f瀽琛ㄥ崟閰嶇疆
         Map<String, BpmFormFieldVO> formFieldsMap = new LinkedHashMap<>();
diff --git a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
index b0206c3..0175358 100644
--- a/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
+++ b/yudao-module-bpm/src/main/java/cn/iocoder/yudao/module/bpm/service/message/BpmMessageServiceImpl.java
@@ -1,23 +1,18 @@
 package cn.iocoder.yudao.module.bpm.service.message;
 
-import cn.iocoder.yudao.framework.web.config.WebProperties;
-import cn.iocoder.yudao.module.bpm.convert.message.BpmMessageConvert;
-import cn.iocoder.yudao.module.bpm.enums.message.BpmMessageEnum;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceApproveReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenProcessInstanceRejectReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskCreatedReqDTO;
 import cn.iocoder.yudao.module.bpm.service.message.dto.BpmMessageSendWhenTaskTimeoutReqDTO;
-import cn.iocoder.yudao.module.system.api.sms.SmsSendApi;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
-import jakarta.annotation.Resource;
-import java.util.HashMap;
-import java.util.Map;
-
 /**
  * BPM 娑堟伅 Service 瀹炵幇绫�
+ *
+ * 娉ㄦ剰锛氬凡鍏抽棴鐭俊閫氱煡鍔熻兘锛岀浉鍏虫柟娉曚粎璁板綍鏃ュ織锛屼笉鍐嶈皟鐢ㄧ煭淇″彂閫佹帴鍙o紝
+ * 浠ラ伩鍏嶁�滅煭淇℃ā鏉夸笉瀛樺湪鈥濈瓑闂銆�
  *
  * @author 瓒呯骇绠$悊鍛�
  */
@@ -26,54 +21,24 @@
 @Slf4j
 public class BpmMessageServiceImpl implements BpmMessageService {
 
-    @Resource
-    private SmsSendApi smsSendApi;
-
-    @Resource
-    private WebProperties webProperties;
-
     @Override
     public void sendMessageWhenProcessInstanceApprove(BpmMessageSendWhenProcessInstanceApproveReqDTO reqDTO) {
-        Map<String, Object> templateParams = new HashMap<>();
-        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
-        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
-        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
-                BpmMessageEnum.PROCESS_INSTANCE_APPROVE.getSmsTemplateCode(), templateParams));
+        log.info("[sendMessageWhenProcessInstanceApprove][宸插叧闂煭淇¢�氱煡锛岃烦杩囧彂閫乚[processInstanceId: {}]", reqDTO.getProcessInstanceId());
     }
 
     @Override
     public void sendMessageWhenProcessInstanceReject(BpmMessageSendWhenProcessInstanceRejectReqDTO reqDTO) {
-        Map<String, Object> templateParams = new HashMap<>();
-        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
-        templateParams.put("reason", reqDTO.getReason());
-        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
-        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getStartUserId(),
-                BpmMessageEnum.PROCESS_INSTANCE_REJECT.getSmsTemplateCode(), templateParams));
+        log.info("[sendMessageWhenProcessInstanceReject][宸插叧闂煭淇¢�氱煡锛岃烦杩囧彂閫乚[processInstanceId: {}]", reqDTO.getProcessInstanceId());
     }
 
     @Override
     public void sendMessageWhenTaskAssigned(BpmMessageSendWhenTaskCreatedReqDTO reqDTO) {
-        Map<String, Object> templateParams = new HashMap<>();
-        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
-        templateParams.put("taskName", reqDTO.getTaskName());
-        templateParams.put("startUserNickname", reqDTO.getStartUserNickname());
-        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
-        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
-                BpmMessageEnum.TASK_ASSIGNED.getSmsTemplateCode(), templateParams));
+        log.info("[sendMessageWhenTaskAssigned][宸插叧闂煭淇¢�氱煡锛岃烦杩囧彂閫乚[processInstanceId: {}]", reqDTO.getProcessInstanceId());
     }
 
     @Override
     public void sendMessageWhenTaskTimeout(BpmMessageSendWhenTaskTimeoutReqDTO reqDTO) {
-        Map<String, Object> templateParams = new HashMap<>();
-        templateParams.put("processInstanceName", reqDTO.getProcessInstanceName());
-        templateParams.put("taskName", reqDTO.getTaskName());
-        templateParams.put("detailUrl", getProcessInstanceDetailUrl(reqDTO.getProcessInstanceId()));
-        smsSendApi.sendSingleSmsToAdmin(BpmMessageConvert.INSTANCE.convert(reqDTO.getAssigneeUserId(),
-                BpmMessageEnum.TASK_TIMEOUT.getSmsTemplateCode(), templateParams));
-    }
-
-    private String getProcessInstanceDetailUrl(String taskId) {
-        return webProperties.getAdminUi().getUrl() + "/bpm/process-instance/detail?id=" + taskId;
+        log.info("[sendMessageWhenTaskTimeout][宸插叧闂煭淇¢�氱煡锛岃烦杩囧彂閫乚[processInstanceId: {}]", reqDTO.getProcessInstanceId());
     }
 
 }
diff --git a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java
index f9fbf94..eea5ec0 100644
--- a/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java
+++ b/yudao-module-bpm/src/test/java/cn/iocoder/yudao/module/bpm/framework/flowable/core/util/FlowableUtilsTest.java
@@ -92,6 +92,16 @@
     }
 
     @Test
+    public void testGetSummary_normalFormWithoutFields_returnEmpty() {
+        // 鍑嗗鍙傛暟锛氫笟鍔¤〃鍗曟垨鍘嗗彶娴佺▼瀹氫箟鍙兘娌℃湁鍐椾綑 formFields銆�
+        BpmProcessDefinitionInfoDO processDefinitionInfo = processDefinitionInfo(null, null);
+
+        // 璋冪敤 & 鏂█锛氱己灏戞憳瑕佸瓧娈垫椂杩斿洖绌烘憳瑕侊紝涓嶈兘褰卞搷鏁翠釜鍒嗛〉鎺ュ彛銆�
+        assertEquals(Collections.emptyList(),
+                FlowableUtils.getSummary(processDefinitionInfo, Collections.emptyMap()));
+    }
+
+    @Test
     public void testGetSummary_notNormalForm_returnNull() {
         // 鍑嗗鍙傛暟
         BpmProcessDefinitionInfoDO processDefinitionInfo = BpmProcessDefinitionInfoDO.builder()
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 df706ac..f779a39 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/ErpPurchaseOrderController.java
@@ -13,6 +13,7 @@
 import cn.iocoder.yudao.module.erp.controller.admin.purchase.vo.order.ErpPurchaseOrderSaveReqVO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO;
 import cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderItemDO;
+import cn.iocoder.yudao.module.erp.enums.ErpAuditStatus;
 import cn.iocoder.yudao.module.erp.enums.ErpPurchaseOrderSourceTypeEnum;
 import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
 import cn.iocoder.yudao.module.srm.api.supplier.dto.SrmSupplierRespDTO;
@@ -40,7 +41,9 @@
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.apilog.core.enums.OperateTypeEnum.EXPORT;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMultiMap;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@@ -170,6 +173,23 @@
         return success(purchaseOrderService.confirmReceipt(id));
     }
 
+    @PostMapping("/reverse-approve")
+    @Operation(summary = "鍙嶅鎵癸紙鍗忓悓鍔炲叕宸插姙浠诲姟涓挙鍥炲凡瀹℃牳璁㈠崟锛岀洿鎺ラ噸鏂拌繘鍏ュ緟鍔炲鎵癸級")
+    @Parameter(name = "processInstanceId", description = "娴佺▼瀹炰緥缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('erp:purchase-order:update-status')")
+    public CommonResult<Boolean> reverseApprove(@RequestParam("processInstanceId") String processInstanceId) {
+        // 1. 鏍规嵁娴佺▼瀹炰緥缂栧彿鎵惧埌閲囪喘璁㈠崟
+        ErpPurchaseOrderDO purchaseOrder = purchaseOrderService.getPurchaseOrderByProcessInstanceId(processInstanceId);
+        if (purchaseOrder == null) {
+            throw exception(PURCHASE_ORDER_NOT_EXISTS);
+        }
+        // 2. 鍙嶅鏍革細宸插鏍� -> 鏈鏍革紙鍚叆搴�/閫�璐ф牎楠岋級
+        purchaseOrderService.updatePurchaseOrderStatus(purchaseOrder.getId(), ErpAuditStatus.PROCESS.getStatus());
+        // 3. 鑷姩閲嶆柊鎻愪氦瀹℃壒 -> 璁㈠崟鐩存帴鍥炲埌鍗忓悓鍔炲叕寰呭姙浠诲姟
+        purchaseOrderService.submitPurchaseOrder(purchaseOrder.getId(), "purchase_order_approve", getLoginUserId());
+        return success(true);
+    }
+
     private PageResult<ErpPurchaseOrderRespVO> buildPurchaseOrderVOPageResult(PageResult<ErpPurchaseOrderDO> pageResult) {
         if (CollUtil.isEmpty(pageResult.getList())) {
             return PageResult.empty(pageResult.getTotal());
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
index 5916e59..b427449 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/controller/admin/purchase/vo/request/ErpPurchaseRequestPageReqVO.java
@@ -9,7 +9,7 @@
 
 import java.time.LocalDateTime;
 
-import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY;
+import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
 
 @Schema(description = "绠$悊鍚庡彴 - ERP 閲囪喘鐢宠鍒嗛〉 Request VO")
 @Data
@@ -33,7 +33,7 @@
     private Integer status;
 
     @Schema(description = "鐢宠鏃堕棿")
-    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY)
+    @DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
     private LocalDateTime[] requestTime;
 
     @Schema(description = "浜у搧缂栧彿", example = "1")
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
index 9f7aca0..1caa99f 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/dal/mysql/purchase/ErpPurchaseOrderMapper.java
@@ -74,6 +74,10 @@
         return selectOne(ErpPurchaseOrderDO::getNo, no);
     }
 
+    default ErpPurchaseOrderDO selectByProcessInstanceId(String processInstanceId) {
+        return selectOne(ErpPurchaseOrderDO::getProcessInstanceId, processInstanceId);
+    }
+
     default List<ErpPurchaseOrderDO> selectList(Long supplierId, Integer status) {
         return selectList(new LambdaQueryWrapperX<ErpPurchaseOrderDO>()
                 .eqIfPresent(ErpPurchaseOrderDO::getSupplierId, supplierId)
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
index 849625d..db7bbee 100644
--- a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/enums/ErpAuditStatus.java
@@ -20,7 +20,8 @@
     PROCESS(10, "鏈鏍�"), // 鏈鏍�/鑽夌
     APPROVING(15, "瀹℃壒涓�"), // 瀹℃壒涓�
     APPROVE(20, "宸插鏍�"), // 瀹℃牳閫氳繃
-    CANCEL(30, "宸蹭綔搴�"); // 宸蹭綔搴�/宸叉挙閿�
+    CANCEL(30, "宸蹭綔搴�"), // 宸蹭綔搴�/宸叉挙閿�
+    REJECT(35, "宸查┏鍥�"); // 瀹℃壒琚┏鍥�
 
     public static final Integer[] ARRAYS = Arrays.stream(values()).map(ErpAuditStatus::getStatus).toArray(Integer[]::new);
 
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 120cbd2..d5c204f 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
@@ -98,6 +98,14 @@
     ErpPurchaseOrderDO getPurchaseOrder(Long id);
 
     /**
+     * 鏍规嵁 BPM 娴佺▼瀹炰緥缂栧彿鏌ヨ閲囪喘璁㈠崟锛堢敤浜庡崗鍚屽姙鍏弽瀹℃壒锛�
+     *
+     * @param processInstanceId 娴佺▼瀹炰緥缂栧彿
+     * @return 閲囪喘璁㈠崟
+     */
+    ErpPurchaseOrderDO getPurchaseOrderByProcessInstanceId(String processInstanceId);
+
+    /**
      * 鏍¢獙閲囪喘璁㈠崟锛屽凡缁忓鏍搁�氳繃
      *
      * @param id 缂栧彿
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 083095f..8809791 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
@@ -41,6 +41,7 @@
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.*;
+import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 import static cn.iocoder.yudao.module.erp.enums.ErrorCodeConstants.*;
 
 /**
@@ -56,7 +57,11 @@
     /**
      * BPM 閲囪喘璁㈠崟瀹℃壒鍒嗙被缂栫爜
      */
-    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_request_approve";
+    private static final String PURCHASE_ORDER_APPROVE_CATEGORY_CODE = "purchase_order_approve";
+    /**
+     * BPM 閲囪喘璁㈠崟瀹℃壒娴佺▼瀹氫箟 Key锛堝垱寤鸿鍗曞悗鑷姩鎻愪氦鍒板崗鍚屽姙鍏鎵癸級
+     */
+    private static final String PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY = "purchase_order_approve";
 
     @Resource
     private ErpPurchaseOrderMapper purchaseOrderMapper;
@@ -119,6 +124,10 @@
         // 2.3 缁戝畾闄勪欢
         storageAttachmentApi.bindAttachments("file", "erp_purchase_order",
                 purchaseOrder.getId(), createReqVO.getBlobIds());
+
+        // 3. 鍒涘缓鍚庤嚜鍔ㄦ彁浜ゅ鎵瑰埌鍗忓悓鍔炲叕锛堝鎵瑰湪鍗忓悓鍔炲叕瀹屾垚锛屼笉鍦ㄦ湰椤甸潰瀹℃壒锛�
+        submitPurchaseOrder(purchaseOrder.getId(), PURCHASE_ORDER_APPROVE_PROCESS_DEFINITION_KEY, getLoginUserId());
+
         return purchaseOrder.getId();
     }
 
@@ -288,7 +297,7 @@
         if (BpmTaskStatusEnum.APPROVE.getStatus().equals(bpmResult)) {
             return ErpAuditStatus.APPROVE.getStatus();
         } else if (BpmTaskStatusEnum.REJECT.getStatus().equals(bpmResult)) {
-            return ErpAuditStatus.PROCESS.getStatus();
+            return ErpAuditStatus.REJECT.getStatus();
         } else if (BpmTaskStatusEnum.CANCEL.getStatus().equals(bpmResult)) {
             return ErpAuditStatus.CANCEL.getStatus();
         }
@@ -406,6 +415,11 @@
     }
 
     @Override
+    public ErpPurchaseOrderDO getPurchaseOrderByProcessInstanceId(String processInstanceId) {
+        return purchaseOrderMapper.selectByProcessInstanceId(processInstanceId);
+    }
+
+    @Override
     public ErpPurchaseOrderDO validatePurchaseOrder(Long id) {
         ErpPurchaseOrderDO purchaseOrder = validatePurchaseOrderExists(id);
         if (ObjectUtil.notEqual(purchaseOrder.getStatus(), ErpAuditStatus.APPROVE.getStatus())) {
diff --git a/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
new file mode 100644
index 0000000..62fdb1d
--- /dev/null
+++ b/yudao-module-erp/src/main/java/cn/iocoder/yudao/module/erp/service/purchase/listener/ErpPurchaseOrderStatusListener.java
@@ -0,0 +1,54 @@
+package cn.iocoder.yudao.module.erp.service.purchase.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.erp.service.purchase.ErpPurchaseOrderService;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.stereotype.Component;
+
+/**
+ * 閲囪喘璁㈠崟瀹℃壒鐘舵�佺洃鍚櫒
+ *
+ * 鐩戝惉 BPM 娴佺▼瀹炰緥鐘舵�佸彉鏇翠簨浠讹紝鏇存柊閲囪喘璁㈠崟鐨勫鎵圭姸鎬侊紙鍗忓悓鍔炲叕瀹℃壒鍚庡洖鍐欙級
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Component
+@Slf4j
+public class ErpPurchaseOrderStatusListener extends BpmProcessInstanceStatusEventListener {
+
+    /**
+     * 閲囪喘璁㈠崟瀹℃壒娴佺▼瀹氫箟 Key
+     */
+    private static final String PROCESS_DEFINITION_KEY = "purchase_order_approve";
+
+    @Resource
+    private ErpPurchaseOrderService purchaseOrderService;
+
+    @Override
+    protected String getProcessDefinitionKey() {
+        return PROCESS_DEFINITION_KEY;
+    }
+
+    @Override
+    protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Long orderId;
+        try {
+            orderId = Long.parseLong(event.getBusinessKey());
+        } catch (NumberFormatException e) {
+            log.warn("[onEvent] businessKey({}) 涓嶆槸鏈夋晥鐨勯噰璐鍗曠紪鍙�", event.getBusinessKey());
+            return;
+        }
+
+        log.info("[onEvent] 鏀跺埌BPM瀹℃壒浜嬩欢锛宱rderId={}, status={}", orderId, event.getStatus());
+
+        try {
+            purchaseOrderService.updatePurchaseOrderAuditStatus(orderId, event.getStatus());
+            log.info("[onEvent] 閲囪喘璁㈠崟({}) 瀹℃壒鐘舵�佹洿鏂板畬鎴�", orderId);
+        } catch (Exception e) {
+            log.error("[onEvent] 鏇存柊閲囪喘璁㈠崟瀹℃壒鐘舵�佸け璐ワ紝orderId={}", orderId, e);
+        }
+    }
+
+}
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 ae8c13c..eca2c1e 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
@@ -21,6 +21,7 @@
 import cn.iocoder.yudao.module.mdm.api.item.dto.MdmItemRespDTO;
 import cn.iocoder.yudao.module.mes.api.mps.MesProMpsApi;
 import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsCreateReqDTO;
+import cn.iocoder.yudao.module.mes.api.mps.dto.MesProMpsRespDTO;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import jakarta.annotation.Resource;
@@ -50,6 +51,11 @@
 @Validated
 @Slf4j
 public class ErpSaleOrderServiceImpl implements ErpSaleOrderService {
+
+    /**
+     * MPS 鐘舵�侊細宸插彇娑堬紙瀵瑰簲 MES 妯″潡 MesProMpsStatusEnum.CANCELED 鐨勫�� 3锛�
+     */
+    private static final Integer MPS_STATUS_CANCELED = 3;
 
     @Resource
     private ErpSaleOrderMapper saleOrderMapper;
@@ -205,6 +211,13 @@
             if (item.getNeedProduction() == null || item.getNeedProduction() != 1) {
                 continue;
             }
+            // 閬垮厤瀹℃壒/鍙嶅鎵归噸澶嶆搷浣滃鑷村悓涓�涓攢鍞鍗曞彿浜х敓澶氭潯 MPS
+            MesProMpsRespDTO existingMps = mpsApi.getMpsBySaleOrderItemId(item.getId());
+            if (existingMps != null && ObjectUtil.notEqual(existingMps.getStatus(), MPS_STATUS_CANCELED)) {
+                log.info("[createMpsForSaleOrder] 閿�鍞鍗曟槑缁�({}) 宸插瓨鍦� MPS({})锛岀姸鎬�={}锛岃烦杩囧垱寤�",
+                        item.getId(), existingMps.getId(), existingMps.getStatus());
+                continue;
+            }
             try {
                 Long mpsId = createMpsForSaleOrderItem(saleOrder, item);
                 if (mpsId != null) {
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
index dd2eb29..cd8b6d8 100644
--- a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java
@@ -55,6 +55,15 @@
     ErrorCode RESIGNATION_APPLICATION_EXISTS = new ErrorCode(1_030_003_005, "璇ュ憳宸ュ凡瀛樺湪绂昏亴鐢宠锛岃鍕块噸澶嶆彁浜�");
     ErrorCode RESIGNATION_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_003_006, "绂昏亴瀹℃壒娴佺▼涓嶅瓨鍦�");
 
+    // ========== 鍔犵彮鐢宠 1-030-008-000 ==========
+    ErrorCode OVERTIME_APPLICATION_NOT_EXISTS = new ErrorCode(1_030_008_000, "鍔犵彮鐢宠涓嶅瓨鍦�");
+    ErrorCode OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_001, "鍔犵彮鐢宠鏇存柊澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode OVERTIME_APPLICATION_SUBMIT_FAIL_NOT_DRAFT = new ErrorCode(1_030_008_002, "鍔犵彮鐢宠鎻愪氦澶辫触锛屽師鍥狅細涓嶆槸鑽夌鐘舵��");
+    ErrorCode OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS = new ErrorCode(1_030_008_003, "鏇存柊鍔犵彮鐢宠瀹℃壒鐘舵�佸け璐ワ紝鍘熷洜锛氫笉鏄鎵逛腑鐘舵��");
+    ErrorCode OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS = new ErrorCode(1_030_008_004, "鍔犵彮瀹℃壒娴佺▼涓嶅瓨鍦紝璇峰厛鍦ㄥ崗鍚屽姙鍏腑閰嶇疆骞跺彂甯冩祦绋�");
+    ErrorCode OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS = new ErrorCode(1_030_008_005, "褰撳墠璐﹀彿鏈叧鑱斿憳宸ユ。妗�");
+    ErrorCode OVERTIME_APPLICATION_TIME_INVALID = new ErrorCode(1_030_008_006, "鍔犵彮鏃舵鎴栨墸闄ゆ椂闀夸笉鍚堟硶");
+
     // ========== 鐢ㄦ埛鏁版嵁浜ゆ帴 1-030-007-000 ==========
     ErrorCode USER_HANDOVER_NOT_EXISTS = new ErrorCode(1_030_007_000, "鐢ㄦ埛浜ゆ帴璁板綍涓嶅瓨鍦�");
     ErrorCode USER_HANDOVER_ALREADY_EXISTS = new ErrorCode(1_030_007_001, "璇ョ鑱岀敵璇峰凡瀛樺湪浜ゆ帴璁板綍");
@@ -93,6 +102,7 @@
     ErrorCode EMPLOYEE_SOCIAL_SECURITY_NOT_EXISTS = new ErrorCode(1_030_005_009, "鍛樺伐绀句繚鍏Н閲戞。妗堜笉瀛樺湪");
     ErrorCode SOCIAL_SECURITY_SCHEME_NOT_EXISTS = new ErrorCode(1_030_005_010, "绀句繚鍏Н閲戞柟妗堜笉瀛樺湪");
     ErrorCode LEAVE_TYPE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_011, "璇峰亣绫诲瀷鎵f閰嶇疆涓嶅瓨鍦�");
+    ErrorCode LEAVE_TYPE_CONFIG_TYPE_EXISTS = new ErrorCode(1_030_005_022, "璇ヨ鍋囩被鍨嬫墸娆鹃厤缃凡瀛樺湪");
     ErrorCode SALARY_PAYMENT_EXISTS = new ErrorCode(1_030_005_012, "璇ュ懆鏈熻柂璧勫彂鏀惧彴璐﹀凡瀛樺湪");
     ErrorCode SALARY_CALCULATION_NO_CONFIRMED = new ErrorCode(1_030_005_013, "璇ュ懆鏈熸病鏈夊凡纭鐨勮柂閰牳绠楄褰�");
     ErrorCode SALARY_PAYMENT_ALREADY_PAY = new ErrorCode(1_030_005_014, "钖祫鍙戞斁鍙拌处宸插彂鏀撅紝璇峰嬁閲嶅鎿嶄綔");
@@ -106,5 +116,7 @@
     ErrorCode SALARY_PAYMENT_REVOKE_FAIL = new ErrorCode(1_030_005_022, "鎾ら攢鍙戞斁澶辫触锛屽師鍥狅細璇ユ槑缁嗘湭鍙戞斁");
     ErrorCode SALARY_PAYMENT_DELETE_FAIL = new ErrorCode(1_030_005_023, "鍒犻櫎澶辫触锛屽師鍥狅細浠呭緟鍙戞斁鐘舵�佸彲鍒犻櫎锛岃鍏堟挙閿�宸插彂鏀炬槑缁�");
     ErrorCode SALARY_PAYMENT_HAS_PAID_DETAIL = new ErrorCode(1_030_005_024, "鍒犻櫎澶辫触锛屽師鍥狅細瀛樺湪宸插彂鏀炬槑缁嗭紝璇峰厛鎾ら攢");
+    ErrorCode SALARY_STRUCTURE_NAME_EXISTS = new ErrorCode(1_030_005_025, "钖叕缁撴瀯鍚嶇О宸插瓨鍦�");
+    ErrorCode SALARY_STRUCTURE_IN_USE = new ErrorCode(1_030_005_026, "钖叕缁撴瀯姝e湪琚憳宸ヤ娇鐢紝鏃犳硶鍒犻櫎");
 
 }
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmOvertimeTypeEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmOvertimeTypeEnum.java
new file mode 100644
index 0000000..8886910
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmOvertimeTypeEnum.java
@@ -0,0 +1,31 @@
+package cn.iocoder.yudao.module.hrm.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.math.BigDecimal;
+import java.util.Arrays;
+
+/** 鍔犵彮绫诲瀷鍙婃硶瀹氬伐璧勫�嶇巼銆� */
+@Getter
+@RequiredArgsConstructor
+public enum HrmOvertimeTypeEnum {
+
+    WORKDAY(1, "宸ヤ綔鏃ュ姞鐝�", new BigDecimal("1.5")),
+    REST_DAY(2, "浼戞伅鏃ュ姞鐝�", new BigDecimal("2.0")),
+    LEGAL_HOLIDAY(3, "娉曞畾鑺傚亣鏃ュ姞鐝�", new BigDecimal("3.0"));
+
+    private final Integer type;
+    private final String name;
+    private final BigDecimal salaryMultiplier;
+
+    public static HrmOvertimeTypeEnum valueOfType(Integer type) {
+        return Arrays.stream(values()).filter(item -> item.type.equals(type)).findFirst().orElse(null);
+    }
+
+    public static String getName(Integer type) {
+        HrmOvertimeTypeEnum value = valueOfType(type);
+        return value == null ? "" : value.name;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmOvertimeApplicationController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmOvertimeApplicationController.java
new file mode 100644
index 0000000..5d616ee
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/HrmOvertimeApplicationController.java
@@ -0,0 +1,71 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
+import cn.iocoder.yudao.framework.security.core.service.SecurityFrameworkService;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.*;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmOvertimeApplicationService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+import java.util.Map;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍔犵彮鐢宠")
+@RestController
+@RequestMapping("/hrm/overtime")
+@Validated
+public class HrmOvertimeApplicationController {
+    @Resource private HrmOvertimeApplicationService service;
+    @Resource private SecurityFrameworkService securityFrameworkService;
+
+    @PostMapping("/create") @PreAuthorize("@ss.hasPermission('hrm:overtime:create')")
+    public CommonResult<Long> create(@Valid @RequestBody HrmOvertimeApplicationSaveReqVO reqVO) {
+        return success(service.create(reqVO, SecurityFrameworkUtils.getLoginUserId()));
+    }
+    @PutMapping("/update") @PreAuthorize("@ss.hasPermission('hrm:overtime:update')")
+    public CommonResult<Boolean> update(@Valid @RequestBody HrmOvertimeApplicationSaveReqVO reqVO) {
+        service.update(reqVO, SecurityFrameworkUtils.getLoginUserId()); return success(true);
+    }
+    @DeleteMapping("/delete") @PreAuthorize("@ss.hasPermission('hrm:overtime:delete')")
+    public CommonResult<Boolean> delete(@RequestParam Long id) {
+        service.delete(id, SecurityFrameworkUtils.getLoginUserId()); return success(true);
+    }
+    @GetMapping("/get") @PreAuthorize("@ss.hasAnyPermissions('hrm:overtime:query', 'bpm:task:query')")
+    public CommonResult<HrmOvertimeApplicationRespVO> get(@RequestParam Long id) {
+        return success(service.getDetail(id, SecurityFrameworkUtils.getLoginUserId(), canManage()));
+    }
+    @GetMapping("/current-applicant") @PreAuthorize("@ss.hasPermission('hrm:overtime:create')")
+    public CommonResult<HrmOvertimeApplicationRespVO> currentApplicant() {
+        return success(service.getCurrentApplicant(SecurityFrameworkUtils.getLoginUserId()));
+    }
+    @GetMapping("/page") @PreAuthorize("@ss.hasPermission('hrm:overtime:query')")
+    public CommonResult<PageResult<HrmOvertimeApplicationRespVO>> page(@Valid HrmOvertimeApplicationPageReqVO reqVO) {
+        if (!canManage()) {
+            reqVO.setUserId(SecurityFrameworkUtils.getLoginUserId());
+        }
+        return success(service.getPage(reqVO));
+    }
+    @PostMapping("/submit") @PreAuthorize("@ss.hasPermission('hrm:overtime:submit')")
+    public CommonResult<Boolean> submit(@RequestParam Long id, @RequestParam String processDefinitionKey) {
+        service.submit(id, processDefinitionKey, SecurityFrameworkUtils.getLoginUserId()); return success(true);
+    }
+    @PostMapping("/cancel") @PreAuthorize("@ss.hasPermission('hrm:overtime:cancel')")
+    public CommonResult<Boolean> cancel(@RequestParam Long id) {
+        service.cancel(id, SecurityFrameworkUtils.getLoginUserId()); return success(true);
+    }
+    @GetMapping("/approve-process-definition-list") @PreAuthorize("@ss.hasPermission('hrm:overtime:query')")
+    public CommonResult<List<Map<String, Object>>> processes() { return success(service.getApproveProcessDefinitionList()); }
+
+    private boolean canManage() {
+        return securityFrameworkService.hasPermission("hrm:overtime:manage");
+    }
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationPageReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationPageReqVO.java
new file mode 100644
index 0000000..0a1912d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationPageReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
+
+import cn.iocoder.yudao.framework.common.pojo.PageParam;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+import java.time.LocalDate;
+
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class HrmOvertimeApplicationPageReqVO extends PageParam {
+    private String no;
+    private Long userId;
+    private Long deptId;
+    private LocalDate overtimeDate;
+    private Integer overtimeType;
+    private Integer status;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationRespVO.java
new file mode 100644
index 0000000..01fc937
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationRespVO.java
@@ -0,0 +1,41 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍔犵彮鐢宠 Response VO")
+@Data
+public class HrmOvertimeApplicationRespVO {
+    private Long id;
+    private String no;
+    private Long userId;
+    private Long employeeId;
+    private String employeeName;
+    private String employeeNo;
+    private Long deptId;
+    private String deptName;
+    private String postName;
+    private LocalDate overtimeDate;
+    private LocalTime startTime;
+    private LocalTime endTime;
+    private BigDecimal duration;
+    private Integer overtimeType;
+    private String overtimeTypeName;
+    private Boolean crossDay;
+    private Boolean mealDeduction;
+    private BigDecimal mealDeductionHours;
+    private String reason;
+    private Integer relatedType;
+    private String relatedCode;
+    private String workContent;
+    private Integer status;
+    private String statusName;
+    private String processInstanceId;
+    private LocalDateTime approveTime;
+    private LocalDateTime createTime;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationSaveReqVO.java
new file mode 100644
index 0000000..ffc71d5
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/approval/vo/HrmOvertimeApplicationSaveReqVO.java
@@ -0,0 +1,36 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.approval.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍔犵彮鐢宠鏂板/淇敼 Request VO")
+@Data
+public class HrmOvertimeApplicationSaveReqVO {
+    private Long id;
+    @NotNull(message = "鍔犵彮鏃ユ湡涓嶈兘涓虹┖")
+    private LocalDate overtimeDate;
+    @NotNull(message = "寮�濮嬫椂闂翠笉鑳戒负绌�")
+    private LocalTime startTime;
+    @NotNull(message = "缁撴潫鏃堕棿涓嶈兘涓虹┖")
+    private LocalTime endTime;
+    @NotNull(message = "鍔犵彮鏃堕暱涓嶈兘涓虹┖")
+    @DecimalMin(value = "0.5", message = "鍔犵彮鏃堕暱涓嶈兘灏忎簬 0.5 灏忔椂")
+    private BigDecimal duration;
+    @NotNull(message = "鍔犵彮绫诲瀷涓嶈兘涓虹┖")
+    private Integer overtimeType;
+    private Boolean crossDay;
+    private Boolean mealDeduction;
+    private BigDecimal mealDeductionHours;
+    @NotBlank(message = "鍔犵彮鍘熷洜涓嶈兘涓虹┖")
+    private String reason;
+    private Integer relatedType;
+    private String relatedCode;
+    private String workContent;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
index e99c45f..9f94ad3 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/HrmAttendanceRecordController.java
@@ -7,6 +7,8 @@
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportExcelVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordImportReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordPageReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo.HrmAttendanceRecordRespVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.attendance.HrmAttendanceRecordDO;
@@ -142,6 +144,7 @@
             HrmEmployeeDO employee = employeeMap.get(respVO.getUserId());
             if (employee != null) {
                 respVO.setUserName(employee.getName());
+                respVO.setPhone(employee.getPhone());
             }
             // 閮ㄩ棬鍚嶇О
             DeptRespDTO dept = deptMap.get(respVO.getDeptId());
@@ -166,6 +169,7 @@
             HrmEmployeeDO employee = employeeService.getEmployee(attendanceRecord.getUserId());
             if (employee != null) {
                 respVO.setUserName(employee.getName());
+                respVO.setPhone(employee.getPhone());
             }
         }
         if (attendanceRecord.getDeptId() != null) {
@@ -177,6 +181,104 @@
         return success(respVO);
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "涓嬭浇鑰冨嫟璁板綍瀵煎叆妯℃澘")
+    @PreAuthorize("@ss.hasPermission('hrm:attendance-record:import')")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<HrmAttendanceRecordImportExcelVO> list = Collections.singletonList(
+                HrmAttendanceRecordImportExcelVO.builder()
+                        .userName("寮犱笁")
+                        .phone("13800138000")
+                        .date(java.time.LocalDate.of(2026, 1, 15))
+                        .clockInTime(java.time.LocalDateTime.of(2026, 1, 15, 8, 30))
+                        .clockOutTime(java.time.LocalDateTime.of(2026, 1, 15, 17, 30))
+                        .clockInType(1)
+                        .clockOutType(1)
+                        .location("鍔炲叕鍖�")
+                        .remark("绀轰緥鏁版嵁")
+                        .build()
+        );
+        ExcelUtils.write(response, "鑰冨嫟瀵煎叆妯℃澘.xls", "鑰冨嫟璁板綍", HrmAttendanceRecordImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆鑰冨嫟璁板綍")
+    @PreAuthorize("@ss.hasPermission('hrm:attendance-record:import')")
+    public CommonResult<String> importExcel(@Valid HrmAttendanceRecordImportReqVO importReqVO) throws IOException {
+        List<HrmAttendanceRecordImportExcelVO> list = ExcelUtils.read(importReqVO.getFile(), HrmAttendanceRecordImportExcelVO.class);
+        if (CollUtil.isEmpty(list)) {
+            return success("娌℃湁鍙鍏ョ殑鏁版嵁");
+        }
+        int successCount = 0;
+        Set<String> missingNames = new LinkedHashSet<>();
+        Set<String> duplicateNames = new LinkedHashSet<>();
+        Set<String> invalidRows = new LinkedHashSet<>();
+        for (HrmAttendanceRecordImportExcelVO item : list) {
+            if (item.getDate() == null) {
+                invalidRows.add((item.getUserName() == null || item.getUserName().isBlank()) ? "鏈~濮撳悕鐨勮" : "鑰冨嫟鏃ユ湡涓虹┖(" + item.getUserName().trim() + ")");
+                continue;
+            }
+            if (item.getUserName() == null || item.getUserName().isBlank()) {
+                invalidRows.add("鑰冨嫟鏃ユ湡涓� " + item.getDate() + " 浣嗘湭濉憳宸ュ鍚�");
+                continue;
+            }
+            List<HrmEmployeeDO> employees = employeeService.getEmployeeListByName(item.getUserName().trim());
+            if (employees.isEmpty()) {
+                missingNames.add(item.getUserName().trim());
+                continue;
+            }
+            HrmEmployeeDO employee = employees.get(0);
+            // 瀛樺湪澶氫釜鍚屽悕鍛樺伐鏃讹紝鐢ㄦ墜鏈哄彿鍖哄垎
+            if (employees.size() > 1) {
+                String phone = item.getPhone() != null ? item.getPhone().trim() : null;
+                List<HrmEmployeeDO> matchedByPhone = new ArrayList<>();
+                if (phone != null && !phone.isEmpty()) {
+                    for (HrmEmployeeDO emp : employees) {
+                        if (phone.equals(emp.getPhone())) {
+                            matchedByPhone.add(emp);
+                        }
+                    }
+                }
+                if (matchedByPhone.size() == 1) {
+                    employee = matchedByPhone.get(0);
+                } else {
+                    duplicateNames.add(item.getUserName().trim());
+                    continue;
+                }
+            }
+            HrmAttendanceRecordDO record = attendanceRecordService.getAttendanceRecordByUserIdAndDate(employee.getId(), item.getDate());
+            if (record == null) {
+                record = HrmAttendanceRecordDO.builder()
+                        .userId(employee.getId()).deptId(employee.getDeptId()).date(item.getDate())
+                        .clockInTime(item.getClockInTime()).clockOutTime(item.getClockOutTime())
+                        .clockInType(item.getClockInType()).clockOutType(item.getClockOutType())
+                        .location(item.getLocation()).remark(item.getRemark())
+                        .dataSource(2).build();
+                attendanceRecordService.createImportedRecord(record);
+            } else if (Boolean.TRUE.equals(importReqVO.getUpdateSupport())) {
+                record.setClockInTime(item.getClockInTime());
+                record.setClockOutTime(item.getClockOutTime());
+                record.setClockInType(item.getClockInType());
+                record.setClockOutType(item.getClockOutType());
+                record.setLocation(item.getLocation());
+                record.setRemark(item.getRemark());
+                attendanceRecordService.updateImportedRecord(record);
+            }
+            successCount++;
+        }
+        StringBuilder msg = new StringBuilder("鎴愬姛瀵煎叆 " + successCount + " 鏉�");
+        if (!missingNames.isEmpty()) {
+            msg.append("锛涗互涓嬪憳宸ヤ笉瀛樺湪锛屾湭鑳藉鍏ワ細").append(String.join("銆�", missingNames));
+        }
+        if (!duplicateNames.isEmpty()) {
+            msg.append("锛涗互涓嬪憳宸ュ瓨鍦ㄥ鍚嶅悓鍚嶅憳宸ワ紝闇�琛ュ厖鎵嬫満鍙峰尯鍒嗭紝鏈兘瀵煎叆锛�").append(String.join("銆�", duplicateNames));
+        }
+        if (!invalidRows.isEmpty()) {
+            msg.append("锛涗互涓嬭褰曟暟鎹笉瀹屾暣锛屾湭鑳藉鍏ワ細").append(String.join("銆�", invalidRows));
+        }
+        return success(msg.toString());
+    }
+
     @GetMapping("/export-excel")
     @Operation(summary = "瀵煎嚭鑰冨嫟璁板綍 Excel")
     @PreAuthorize("@ss.hasPermission('hrm:attendance:export')")
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportExcelVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportExcelVO.java
new file mode 100644
index 0000000..44e4ee1
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportExcelVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.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 lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+
+/**
+ * 鑰冨嫟璁板綍 Excel 瀵煎叆妯℃澘 VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class HrmAttendanceRecordImportExcelVO {
+
+    @ExcelProperty("鍛樺伐濮撳悕")
+    private String userName;
+
+    @ExcelProperty("鎵嬫満鍙�")
+    private String phone;
+
+    @ExcelProperty("鑰冨嫟鏃ユ湡")
+    private LocalDate date;
+
+    @ExcelProperty("涓婄彮鎵撳崱鏃堕棿")
+    private LocalDateTime clockInTime;
+
+    @ExcelProperty("涓嬬彮鎵撳崱鏃堕棿")
+    private LocalDateTime clockOutTime;
+
+    @ExcelProperty(value = "涓婄彮鎵撳崱绫诲瀷", converter = DictConvert.class)
+    @DictFormat("hrm_clock_type")
+    private Integer clockInType;
+
+    @ExcelProperty(value = "涓嬬彮鎵撳崱绫诲瀷", converter = DictConvert.class)
+    @DictFormat("hrm_clock_type")
+    private Integer clockOutType;
+
+    @ExcelProperty("鎵撳崱鍦扮偣")
+    private String location;
+
+    @ExcelProperty("澶囨敞")
+    private String remark;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportReqVO.java
new file mode 100644
index 0000000..c509a13
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordImportReqVO.java
@@ -0,0 +1,18 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.attendance.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+import org.springframework.web.multipart.MultipartFile;
+
+@Schema(description = "绠$悊鍚庡彴 - 鑰冨嫟璁板綍瀵煎叆 Request VO")
+@Data
+public class HrmAttendanceRecordImportReqVO {
+
+    @Schema(description = "Excel 鏂囦欢", requiredMode = Schema.RequiredMode.REQUIRED)
+    @NotNull(message = "Excel 鏂囦欢涓嶈兘涓虹┖")
+    private MultipartFile file;
+
+    @Schema(description = "鏄惁瑕嗙洊宸插瓨鍦ㄧ殑鏁版嵁")
+    private Boolean updateSupport = false;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java
index 67215a6..7b91afa 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/attendance/vo/HrmAttendanceRecordRespVO.java
@@ -34,6 +34,10 @@
     @ExcelProperty("閮ㄩ棬鍚嶇О")
     private String deptName;
 
+    @Schema(description = "鎵嬫満鍙�")
+    @ExcelProperty("鎵嬫満鍙�")
+    private String phone;
+
     @Schema(description = "鑰冨嫟鏃ユ湡")
     @ExcelProperty("鑰冨嫟鏃ユ湡")
     private LocalDate date;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java
index bc1898b..5c1af32 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmLeaveTypeConfigController.java
@@ -2,6 +2,7 @@
 
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmLeaveTypeConfigCreateReqVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmLeaveTypeConfigRespVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmLeaveTypeConfigSaveReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmLeaveTypeConfigDO;
@@ -30,7 +31,7 @@
 
     @GetMapping("/list")
     @Operation(summary = "鑾峰彇鎵�鏈夎鍋囩被鍨嬮厤缃垪琛�")
-    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    @PreAuthorize("@ss.hasPermission('hrm:leave-type-config:query')")
     public CommonResult<List<HrmLeaveTypeConfigRespVO>> getLeaveTypeConfigList() {
         List<HrmLeaveTypeConfigDO> list = leaveTypeConfigService.getLeaveTypeConfigList();
         return success(BeanUtils.toBean(list, HrmLeaveTypeConfigRespVO.class));
@@ -39,19 +40,37 @@
     @GetMapping("/get")
     @Operation(summary = "鑾峰彇璇峰亣绫诲瀷閰嶇疆")
     @Parameter(name = "leaveType", description = "璇峰亣绫诲瀷", required = true, example = "1")
-    @PreAuthorize("@ss.hasPermission('hrm:salary:query')")
+    @PreAuthorize("@ss.hasPermission('hrm:leave-type-config:query')")
     public CommonResult<HrmLeaveTypeConfigRespVO> getLeaveTypeConfig(@RequestParam("leaveType") Integer leaveType) {
         HrmLeaveTypeConfigDO config = leaveTypeConfigService.getLeaveTypeConfig(leaveType);
         return success(BeanUtils.toBean(config, HrmLeaveTypeConfigRespVO.class));
     }
 
+    @PostMapping("/create")
+    @Operation(summary = "鏂板璇峰亣绫诲瀷閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('hrm:leave-type-config:create')")
+    public CommonResult<Long> createLeaveTypeConfig(@Valid @RequestBody HrmLeaveTypeConfigCreateReqVO createReqVO) {
+        HrmLeaveTypeConfigDO config = BeanUtils.toBean(createReqVO, HrmLeaveTypeConfigDO.class);
+        leaveTypeConfigService.createLeaveTypeConfig(config);
+        return success(config.getId());
+    }
+
     @PutMapping("/update")
     @Operation(summary = "鏇存柊璇峰亣绫诲瀷閰嶇疆")
-    @PreAuthorize("@ss.hasPermission('hrm:salary:update')")
+    @PreAuthorize("@ss.hasPermission('hrm:leave-type-config:update')")
     public CommonResult<Boolean> updateLeaveTypeConfig(@Valid @RequestBody HrmLeaveTypeConfigSaveReqVO updateReqVO) {
         HrmLeaveTypeConfigDO config = BeanUtils.toBean(updateReqVO, HrmLeaveTypeConfigDO.class);
         leaveTypeConfigService.updateLeaveTypeConfig(config);
         return success(true);
     }
 
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎璇峰亣绫诲瀷閰嶇疆")
+    @Parameter(name = "id", description = "閰嶇疆ID", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:leave-type-config:delete')")
+    public CommonResult<Boolean> deleteLeaveTypeConfig(@RequestParam("id") Long id) {
+        leaveTypeConfigService.deleteLeaveTypeConfig(id);
+        return success(true);
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java
new file mode 100644
index 0000000..9f230b6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSalaryStructureController.java
@@ -0,0 +1,81 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructureRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryStructureSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmSalaryStructureService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.DeleteMapping;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.PutMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestParam;
+import org.springframework.web.bind.annotation.RestController;
+
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 钖叕缁撴瀯")
+@RestController
+@RequestMapping("/hrm/salary/structure")
+@Validated
+public class HrmSalaryStructureController {
+
+    @Resource
+    private HrmSalaryStructureService salaryStructureService;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓钖叕缁撴瀯")
+    @PreAuthorize("@ss.hasPermission('hrm:salary-structure:create')")
+    public CommonResult<Long> createSalaryStructure(@Valid @RequestBody HrmSalaryStructureSaveReqVO reqVO) {
+        return success(salaryStructureService.createSalaryStructure(
+                BeanUtils.toBean(reqVO, HrmSalaryStructureDO.class)));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊钖叕缁撴瀯")
+    @PreAuthorize("@ss.hasPermission('hrm:salary-structure:update')")
+    public CommonResult<Boolean> updateSalaryStructure(@Valid @RequestBody HrmSalaryStructureSaveReqVO reqVO) {
+        salaryStructureService.updateSalaryStructure(BeanUtils.toBean(reqVO, HrmSalaryStructureDO.class));
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎钖叕缁撴瀯")
+    @Parameter(name = "id", description = "缁撴瀯缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:salary-structure:delete')")
+    public CommonResult<Boolean> deleteSalaryStructure(@RequestParam("id") Long id) {
+        salaryStructureService.deleteSalaryStructure(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱钖叕缁撴瀯")
+    @PreAuthorize("@ss.hasPermission('hrm:salary-structure:query')")
+    public CommonResult<HrmSalaryStructureRespVO> getSalaryStructure(@RequestParam("id") Long id) {
+        return success(BeanUtils.toBean(salaryStructureService.getSalaryStructure(id),
+                HrmSalaryStructureRespVO.class));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰緱钖叕缁撴瀯鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('hrm:salary-structure:query')")
+    public CommonResult<List<HrmSalaryStructureRespVO>> getSalaryStructureList(
+            @RequestParam(value = "name", required = false) String name,
+            @RequestParam(value = "status", required = false) Integer status) {
+        return success(BeanUtils.toBean(salaryStructureService.getSalaryStructureList(name, status),
+                HrmSalaryStructureRespVO.class));
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigCreateReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigCreateReqVO.java
new file mode 100644
index 0000000..1e7bb89
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmLeaveTypeConfigCreateReqVO.java
@@ -0,0 +1,32 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 璇峰亣绫诲瀷鎵f閰嶇疆鏂板 Request VO")
+@Data
+public class HrmLeaveTypeConfigCreateReqVO {
+
+    @Schema(description = "璇峰亣绫诲瀷锛�1-浜嬪亣, 2-鐥呭亣, 3-骞村亣, 4-濠氬亣, 5-浜у亣, 6-涓у亣", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "璇峰亣绫诲瀷涓嶈兘涓虹┖")
+    private Integer leaveType;
+
+    @Schema(description = "绫诲瀷鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "浜嬪亣")
+    @NotBlank(message = "绫诲瀷鍚嶇О涓嶈兘涓虹┖")
+    private String name;
+
+    @Schema(description = "鎵f姣斾緥锛�0-1涔嬮棿锛屽0.5琛ㄧず鎵�50%锛�", requiredMode = Schema.RequiredMode.REQUIRED, example = "0.5")
+    @NotNull(message = "鎵f姣斾緥涓嶈兘涓虹┖")
+    private BigDecimal deductionRatio;
+
+    @Schema(description = "澶囨敞")
+    private String remark;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤, 1-绂佺敤")
+    private Integer status;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java
new file mode 100644
index 0000000..6ea2dba
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureRespVO.java
@@ -0,0 +1,30 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.math.BigDecimal;
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕缁撴瀯 Response VO")
+@Data
+public class HrmSalaryStructureRespVO {
+
+    @Schema(description = "缁撴瀯缂栧彿")
+    private Long id;
+    @Schema(description = "缁撴瀯鍚嶇О")
+    private String name;
+    @Schema(description = "宸ヤ綔鏃ュ姞鐝伐璧勫�嶇巼")
+    private BigDecimal overtimePayRatio;
+    @Schema(description = "浼戞伅鏃ュ姞鐝伐璧勫�嶇巼")
+    private BigDecimal restDayOvertimePayRatio;
+    @Schema(description = "娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼")
+    private BigDecimal legalHolidayOvertimePayRatio;
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤")
+    private Integer status;
+    @Schema(description = "澶囨敞")
+    private String remark;
+    @Schema(description = "鍒涘缓鏃堕棿")
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java
new file mode 100644
index 0000000..aad0b4c
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSalaryStructureSaveReqVO.java
@@ -0,0 +1,51 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMax;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 钖叕缁撴瀯鏂板/淇敼 Request VO")
+@Data
+public class HrmSalaryStructureSaveReqVO {
+
+    @Schema(description = "缁撴瀯缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "缁撴瀯鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "鏍囧噯鍛樺伐钖叕")
+    @NotBlank(message = "缁撴瀯鍚嶇О涓嶈兘涓虹┖")
+    @Size(max = 100, message = "缁撴瀯鍚嶇О涓嶈兘瓒呰繃 100 涓瓧绗�")
+    private String name;
+
+    @Schema(description = "宸ヤ綔鏃ュ姞鐝伐璧勫�嶇巼锛�0 琛ㄧず涓嶈鍔犵彮宸ヨ祫", example = "1.50")
+    @NotNull(message = "宸ヤ綔鏃ュ姞鐝�嶇巼涓嶈兘涓虹┖")
+    @DecimalMin(value = "0", message = "宸ヤ綔鏃ュ姞鐝�嶇巼涓嶈兘灏忎簬 0")
+    @DecimalMax(value = "10", message = "宸ヤ綔鏃ュ姞鐝�嶇巼涓嶈兘澶т簬 10")
+    private BigDecimal overtimePayRatio;
+
+    @Schema(description = "浼戞伅鏃ュ姞鐝伐璧勫�嶇巼锛�0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�", example = "2.00")
+    @NotNull(message = "浼戞伅鏃ュ姞鐝�嶇巼涓嶈兘涓虹┖")
+    @DecimalMin(value = "0", message = "浼戞伅鏃ュ姞鐝�嶇巼涓嶈兘灏忎簬 0")
+    @DecimalMax(value = "10", message = "浼戞伅鏃ュ姞鐝�嶇巼涓嶈兘澶т簬 10")
+    private BigDecimal restDayOvertimePayRatio;
+
+    @Schema(description = "娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼锛�0 琛ㄧず涓嶈璇ョ被鍨嬪姞鐝伐璧�", example = "3.00")
+    @NotNull(message = "娉曞畾鑺傚亣鏃ュ姞鐝�嶇巼涓嶈兘涓虹┖")
+    @DecimalMin(value = "0", message = "娉曞畾鑺傚亣鏃ュ姞鐝�嶇巼涓嶈兘灏忎簬 0")
+    @DecimalMax(value = "10", message = "娉曞畾鑺傚亣鏃ュ姞鐝�嶇巼涓嶈兘澶т簬 10")
+    private BigDecimal legalHolidayOvertimePayRatio;
+
+    @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
+    @NotNull(message = "鐘舵�佷笉鑳戒负绌�")
+    private Integer status;
+
+    @Schema(description = "澶囨敞")
+    @Size(max = 500, message = "澶囨敞涓嶈兘瓒呰繃 500 涓瓧绗�")
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmOvertimeApplicationDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmOvertimeApplicationDO.java
new file mode 100644
index 0000000..3bf0e62
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/approval/HrmOvertimeApplicationDO.java
@@ -0,0 +1,46 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.approval;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableId;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.*;
+
+import java.math.BigDecimal;
+import java.time.LocalDate;
+import java.time.LocalDateTime;
+import java.time.LocalTime;
+
+@TableName("hrm_overtime_application")
+@KeySequence("hrm_overtime_application_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmOvertimeApplicationDO extends BaseDO {
+    @TableId
+    private Long id;
+    private String no;
+    /** 绯荤粺鐢ㄦ埛 ID */
+    private Long userId;
+    private Long deptId;
+    private Long employeeId;
+    private LocalDate overtimeDate;
+    private LocalTime startTime;
+    private LocalTime endTime;
+    private BigDecimal duration;
+    private Integer overtimeType;
+    private Boolean crossDay;
+    private Boolean mealDeduction;
+    private BigDecimal mealDeductionHours;
+    private String reason;
+    /** 1-椤圭洰锛�2-宸ュ崟 */
+    private Integer relatedType;
+    private String relatedCode;
+    private String workContent;
+    private Integer status;
+    private String processInstanceId;
+    private LocalDateTime approveTime;
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java
index b467169..9c43e3d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSalaryStructureDO.java
@@ -45,9 +45,19 @@
     private BigDecimal performanceRatio;
 
     /**
-     * 鍔犵彮宸ヨ祫鍊嶇巼
+     * 宸ヤ綔鏃ュ姞鐝伐璧勫�嶇巼
      */
     private BigDecimal overtimePayRatio;
+
+    /**
+     * 浼戞伅鏃ュ姞鐝伐璧勫�嶇巼
+     */
+    private BigDecimal restDayOvertimePayRatio;
+
+    /**
+     * 娉曞畾鑺傚亣鏃ュ姞鐝伐璧勫�嶇巼
+     */
+    private BigDecimal legalHolidayOvertimePayRatio;
 
     /**
      * 绀句繚鍩烘暟
@@ -69,4 +79,4 @@
      */
     private String remark;
 
-}
\ No newline at end of file
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmOvertimeApplicationMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmOvertimeApplicationMapper.java
new file mode 100644
index 0000000..cfcb138
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/approval/HrmOvertimeApplicationMapper.java
@@ -0,0 +1,38 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmOvertimeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmOvertimeApplicationDO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.time.LocalDate;
+import java.util.List;
+
+@Mapper
+public interface HrmOvertimeApplicationMapper extends BaseMapperX<HrmOvertimeApplicationDO> {
+
+    default PageResult<HrmOvertimeApplicationDO> selectPage(HrmOvertimeApplicationPageReqVO reqVO) {
+        return selectPage(reqVO, new LambdaQueryWrapperX<HrmOvertimeApplicationDO>()
+                .likeIfPresent(HrmOvertimeApplicationDO::getNo, reqVO.getNo())
+                .eqIfPresent(HrmOvertimeApplicationDO::getUserId, reqVO.getUserId())
+                .eqIfPresent(HrmOvertimeApplicationDO::getDeptId, reqVO.getDeptId())
+                .eqIfPresent(HrmOvertimeApplicationDO::getOvertimeDate, reqVO.getOvertimeDate())
+                .eqIfPresent(HrmOvertimeApplicationDO::getOvertimeType, reqVO.getOvertimeType())
+                .eqIfPresent(HrmOvertimeApplicationDO::getStatus, reqVO.getStatus())
+                .orderByDesc(HrmOvertimeApplicationDO::getId));
+    }
+
+    default HrmOvertimeApplicationDO selectByProcessInstanceId(String processInstanceId) {
+        return selectOne(HrmOvertimeApplicationDO::getProcessInstanceId, processInstanceId);
+    }
+
+    default List<HrmOvertimeApplicationDO> selectApprovedByUserAndDateRange(Long userId, LocalDate start, LocalDate end) {
+        return selectList(new LambdaQueryWrapperX<HrmOvertimeApplicationDO>()
+                .eq(HrmOvertimeApplicationDO::getUserId, userId)
+                .eq(HrmOvertimeApplicationDO::getStatus, HrmAuditStatusEnum.APPROVE.getStatus())
+                .between(HrmOvertimeApplicationDO::getOvertimeDate, start, end));
+    }
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
index 1e992a2..19ab24b 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/employee/HrmEmployeeMapper.java
@@ -21,6 +21,14 @@
         return selectOne(HrmEmployeeDO::getPhone, phone);
     }
 
+    default HrmEmployeeDO selectByName(String name) {
+        return selectOne(HrmEmployeeDO::getName, name);
+    }
+
+    default List<HrmEmployeeDO> selectListByName(String name) {
+        return selectList(HrmEmployeeDO::getName, name);
+    }
+
     default HrmEmployeeDO selectByEmployeeNo(String employeeNo) {
         return selectOne(HrmEmployeeDO::getEmployeeNo, employeeNo);
     }
@@ -52,4 +60,8 @@
                 .orderByAsc(HrmEmployeeDO::getName));
     }
 
-}
\ No newline at end of file
+    default Long selectCountBySalaryStructureId(Long salaryStructureId) {
+        return selectCount(HrmEmployeeDO::getSalaryStructureId, salaryStructureId);
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
index d05bee2..4eee062 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmEmployeeSalaryMapper.java
@@ -36,4 +36,8 @@
                 .orderByDesc(HrmEmployeeSalaryDO::getEffectiveDate));
     }
 
-}
\ No newline at end of file
+    default Long selectCountBySalaryStructureId(Long salaryStructureId) {
+        return selectCount(HrmEmployeeSalaryDO::getSalaryStructureId, salaryStructureId);
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
index 4759301..72edf01 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSalaryStructureMapper.java
@@ -15,10 +15,19 @@
 @Mapper
 public interface HrmSalaryStructureMapper extends BaseMapperX<HrmSalaryStructureDO> {
 
-    default List<HrmSalaryStructureDO> selectListByStatus(Integer status) {
+    default List<HrmSalaryStructureDO> selectListByCondition(String name, Integer status) {
         return selectList(new LambdaQueryWrapperX<HrmSalaryStructureDO>()
+                .likeIfPresent(HrmSalaryStructureDO::getName, name)
                 .eqIfPresent(HrmSalaryStructureDO::getStatus, status)
                 .orderByAsc(HrmSalaryStructureDO::getName));
     }
 
-}
\ No newline at end of file
+    default List<HrmSalaryStructureDO> selectListByStatus(Integer status) {
+        return selectListByCondition(null, status);
+    }
+
+    default HrmSalaryStructureDO selectByName(String name) {
+        return selectOne(HrmSalaryStructureDO::getName, name);
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
index 44009fd..7f05f3d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/redis/HrmNoRedisDAO.java
@@ -52,6 +52,9 @@
      */
     public static final String RESIGNATION_APPLICATION_NO_PREFIX = "RA";
 
+    /** 鍔犵彮鐢宠缂栧彿鍓嶇紑 */
+    public static final String OVERTIME_APPLICATION_NO_PREFIX = "OT";
+
     /**
      * 鍛樺伐鍚堝悓缂栧彿鍓嶇紑
      */
@@ -100,6 +103,10 @@
         return generate(RESIGNATION_APPLICATION_NO_PREFIX, 4);
     }
 
+    public String generateOvertimeApplicationNo() {
+        return generate(OVERTIME_APPLICATION_NO_PREFIX, 4);
+    }
+
     /**
      * 鐢熸垚鍛樺伐鍚堝悓缂栧彿锛屾牸寮忥細HT + yyyyMMdd + 4浣嶈嚜澧�
      * 渚嬪锛欻T202401010001
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationService.java
new file mode 100644
index 0000000..4cdebad
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationService.java
@@ -0,0 +1,26 @@
+package cn.iocoder.yudao.module.hrm.service.approval;
+
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmOvertimeApplicationPageReqVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmOvertimeApplicationRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.HrmOvertimeApplicationSaveReqVO;
+import jakarta.validation.Valid;
+
+import java.util.List;
+import java.util.Map;
+
+public interface HrmOvertimeApplicationService {
+    String BPM_PROCESS_DEFINITION_KEY = "hrm_overtime";
+    String APPROVE_CATEGORY_CODE = "overtime_approve";
+
+    Long create(@Valid HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId);
+    void update(@Valid HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId);
+    void delete(Long id, Long loginUserId);
+    HrmOvertimeApplicationRespVO getDetail(Long id, Long loginUserId, boolean canManage);
+    HrmOvertimeApplicationRespVO getCurrentApplicant(Long loginUserId);
+    PageResult<HrmOvertimeApplicationRespVO> getPage(HrmOvertimeApplicationPageReqVO reqVO);
+    void submit(Long id, String processDefinitionKey, Long loginUserId);
+    void cancel(Long id, Long loginUserId);
+    List<Map<String, Object>> getApproveProcessDefinitionList();
+    void updateAuditStatus(Long id, Integer status);
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
new file mode 100644
index 0000000..aa9449d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/HrmOvertimeApplicationServiceImpl.java
@@ -0,0 +1,263 @@
+package cn.iocoder.yudao.module.hrm.service.approval;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.NumberUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.bpm.api.task.BpmProcessInstanceApi;
+import cn.iocoder.yudao.module.bpm.api.task.dto.BpmProcessInstanceCreateReqDTO;
+import cn.iocoder.yudao.module.bpm.controller.admin.task.vo.instance.BpmProcessInstanceCancelReqVO;
+import cn.iocoder.yudao.module.bpm.dal.dataobject.definition.BpmProcessDefinitionInfoDO;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmCategoryService;
+import cn.iocoder.yudao.module.bpm.service.definition.BpmProcessDefinitionService;
+import cn.iocoder.yudao.module.bpm.service.task.BpmProcessInstanceService;
+import cn.iocoder.yudao.module.bpm.service.task.BpmTaskService;
+import cn.iocoder.yudao.module.hrm.controller.admin.approval.vo.*;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmOvertimeApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmOvertimeApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.enums.HrmOvertimeTypeEnum;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.flowable.engine.repository.ProcessDefinition;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+import java.time.Duration;
+import java.time.LocalDateTime;
+import java.util.*;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.*;
+
+@Service
+@Slf4j
+public class HrmOvertimeApplicationServiceImpl implements HrmOvertimeApplicationService {
+    @Resource private HrmOvertimeApplicationMapper mapper;
+    @Resource private HrmEmployeeMapper employeeMapper;
+    @Resource private HrmNoRedisDAO noRedisDAO;
+    @Resource private DeptApi deptApi;
+    @Resource private PostApi postApi;
+    @Resource private BpmProcessInstanceApi bpmProcessInstanceApi;
+    @Resource private BpmProcessInstanceService bpmProcessInstanceService;
+    @Resource private BpmTaskService bpmTaskService;
+    @Resource private BpmCategoryService bpmCategoryService;
+    @Resource private BpmProcessDefinitionService bpmProcessDefinitionService;
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public Long create(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) {
+        HrmEmployeeDO employee = requireEmployee(loginUserId);
+        HrmOvertimeApplicationDO application = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class);
+        applyApplicantAndCalculatedTime(application, employee);
+        application.setNo(noRedisDAO.generateOvertimeApplicationNo());
+        application.setStatus(HrmAuditStatusEnum.DRAFT.getStatus());
+        mapper.insert(application);
+        return application.getId();
+    }
+
+    @Override
+    public void update(HrmOvertimeApplicationSaveReqVO reqVO, Long loginUserId) {
+        HrmOvertimeApplicationDO existing = requireOwnedDraft(reqVO.getId(), loginUserId);
+        HrmOvertimeApplicationDO update = BeanUtils.toBean(reqVO, HrmOvertimeApplicationDO.class);
+        applyApplicantAndCalculatedTime(update, requireEmployee(loginUserId));
+        update.setId(existing.getId());
+        mapper.updateById(update);
+    }
+
+    @Override
+    public void delete(Long id, Long loginUserId) {
+        HrmOvertimeApplicationDO application = require(id);
+        boolean deletable = Objects.equals(application.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())
+                || Objects.equals(application.getStatus(), HrmAuditStatusEnum.CANCEL.getStatus());
+        if (!Objects.equals(application.getUserId(), loginUserId) || !deletable) {
+            throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+        }
+        mapper.deleteById(id);
+    }
+
+    @Override
+    public HrmOvertimeApplicationRespVO getDetail(Long id, Long loginUserId, boolean canManage) {
+        HrmOvertimeApplicationDO application = mapper.selectById(id);
+        if (application != null && !canManage && !Objects.equals(application.getUserId(), loginUserId)
+                && !isProcessParticipant(application.getProcessInstanceId(), loginUserId)) {
+            return null;
+        }
+        return application == null ? null : buildResp(application);
+    }
+
+    @Override
+    public HrmOvertimeApplicationRespVO getCurrentApplicant(Long loginUserId) {
+        HrmEmployeeDO employee = requireEmployee(loginUserId);
+        HrmOvertimeApplicationRespVO resp = new HrmOvertimeApplicationRespVO();
+        fillEmployee(resp, employee);
+        return resp;
+    }
+
+    @Override
+    public PageResult<HrmOvertimeApplicationRespVO> getPage(HrmOvertimeApplicationPageReqVO reqVO) {
+        PageResult<HrmOvertimeApplicationDO> page = mapper.selectPage(reqVO);
+        return new PageResult<>(page.getList().stream().map(this::buildResp).toList(), page.getTotal());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void submit(Long id, String processDefinitionKey, Long loginUserId) {
+        HrmOvertimeApplicationDO application = requireOwnedDraft(id, loginUserId);
+        validateProcessDefinitions();
+        ProcessDefinition definition = bpmProcessDefinitionService.getActiveProcessDefinition(processDefinitionKey);
+        if (definition == null || !BPM_PROCESS_DEFINITION_KEY.equals(definition.getKey())) {
+            throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+        String processInstanceId = bpmProcessInstanceApi.createProcessInstance(loginUserId,
+                new BpmProcessInstanceCreateReqDTO().setProcessDefinitionKey(processDefinitionKey)
+                        .setBusinessKey(String.valueOf(id)));
+        mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).processInstanceId(processInstanceId)
+                .status(HrmAuditStatusEnum.PROCESS.getStatus()).build());
+    }
+
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public void cancel(Long id, Long loginUserId) {
+        HrmOvertimeApplicationDO application = require(id);
+        if (!Objects.equals(application.getUserId(), loginUserId)
+                || !Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+            throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+        bpmProcessInstanceService.cancelProcessInstanceByStartUser(loginUserId,
+                new BpmProcessInstanceCancelReqVO().setId(application.getProcessInstanceId()).setReason("鐢宠浜烘挙閿�鍔犵彮鐢宠"));
+    }
+
+    @Override
+    public List<Map<String, Object>> getApproveProcessDefinitionList() {
+        validateProcessDefinitions();
+        List<BpmProcessDefinitionInfoDO> infos = bpmProcessDefinitionService
+                .getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+        Set<String> ids = convertSet(infos, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        Map<String, ProcessDefinition> latest = new HashMap<>();
+        for (ProcessDefinition definition : bpmProcessDefinitionService.getProcessDefinitionList(ids)) {
+            if (definition.isSuspended()) continue;
+            latest.compute(definition.getKey(), (key, old) -> old == null || definition.getVersion() > old.getVersion() ? definition : old);
+        }
+        return latest.values().stream().map(item -> {
+            Map<String, Object> value = new HashMap<>();
+            value.put("id", item.getId()); value.put("key", item.getKey()); value.put("name", item.getName());
+            value.put("version", item.getVersion()); return value;
+        }).collect(Collectors.toList());
+    }
+
+    @Override
+    public void updateAuditStatus(Long id, Integer status) {
+        HrmOvertimeApplicationDO application = require(id);
+        if (!Objects.equals(application.getStatus(), HrmAuditStatusEnum.PROCESS.getStatus())) {
+            throw exception(OVERTIME_APPLICATION_UPDATE_AUDIT_STATUS_FAIL_NOT_PROCESS);
+        }
+        mapper.updateById(HrmOvertimeApplicationDO.builder().id(id).status(status)
+                .approveTime(LocalDateTime.now()).build());
+    }
+
+    private void validateProcessDefinitions() {
+        if (!bpmCategoryService.getCategoryMap(Collections.singleton(APPROVE_CATEGORY_CODE)).containsKey(APPROVE_CATEGORY_CODE)) {
+            throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+        List<BpmProcessDefinitionInfoDO> infos = bpmProcessDefinitionService.getProcessDefinitionInfoListByCategory(APPROVE_CATEGORY_CODE);
+        if (CollUtil.isEmpty(infos)) throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        Set<String> ids = convertSet(infos, BpmProcessDefinitionInfoDO::getProcessDefinitionId);
+        if (bpmProcessDefinitionService.getProcessDefinitionList(ids).stream().noneMatch(item -> !item.isSuspended())) {
+            throw exception(OVERTIME_APPLICATION_BPM_PROCESS_DEFINITION_NOT_EXISTS);
+        }
+    }
+
+    private void applyApplicantAndCalculatedTime(HrmOvertimeApplicationDO application, HrmEmployeeDO employee) {
+        if (HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType()) == null) {
+            throw exception(OVERTIME_APPLICATION_TIME_INVALID);
+        }
+        application.setUserId(employee.getUserId());
+        application.setEmployeeId(employee.getId());
+        application.setDeptId(employee.getDeptId());
+        long minutes = Duration.between(application.getStartTime(), application.getEndTime()).toMinutes();
+        boolean crossDay = minutes <= 0;
+        if (crossDay) minutes += 24 * 60;
+        BigDecimal mealHours = Boolean.TRUE.equals(application.getMealDeduction())
+                ? Optional.ofNullable(application.getMealDeductionHours()).orElse(BigDecimal.ONE) : BigDecimal.ZERO;
+        BigDecimal rawHours = BigDecimal.valueOf(minutes).divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+        if (Boolean.TRUE.equals(application.getMealDeduction())
+                && (mealHours.compareTo(new BigDecimal("0.5")) < 0
+                || mealHours.compareTo(new BigDecimal("4")) > 0
+                || mealHours.compareTo(rawHours) >= 0)) {
+            throw exception(OVERTIME_APPLICATION_TIME_INVALID);
+        }
+        BigDecimal hours = rawHours
+                .subtract(mealHours).multiply(BigDecimal.valueOf(2)).setScale(0, RoundingMode.HALF_UP)
+                .divide(BigDecimal.valueOf(2), 1, RoundingMode.UNNECESSARY);
+        if (hours.compareTo(BigDecimal.ZERO) <= 0) throw exception(OVERTIME_APPLICATION_TIME_INVALID);
+        application.setCrossDay(crossDay);
+        application.setMealDeductionHours(mealHours);
+        application.setDuration(hours);
+    }
+
+    private HrmOvertimeApplicationDO require(Long id) {
+        HrmOvertimeApplicationDO value = mapper.selectById(id);
+        if (value == null) throw exception(OVERTIME_APPLICATION_NOT_EXISTS);
+        return value;
+    }
+
+    private HrmOvertimeApplicationDO requireOwnedDraft(Long id, Long userId) {
+        HrmOvertimeApplicationDO value = require(id);
+        if (!Objects.equals(value.getUserId(), userId)
+                || !Objects.equals(value.getStatus(), HrmAuditStatusEnum.DRAFT.getStatus())) {
+            throw exception(OVERTIME_APPLICATION_UPDATE_FAIL_NOT_DRAFT);
+        }
+        return value;
+    }
+
+    private HrmEmployeeDO requireEmployee(Long userId) {
+        HrmEmployeeDO employee = employeeMapper.selectByUserId(userId);
+        if (employee == null) throw exception(OVERTIME_APPLICATION_EMPLOYEE_NOT_EXISTS);
+        return employee;
+    }
+
+    /** 褰撳墠寰呭姙瀹℃壒浜烘垨鏇惧鐞嗚繃璇ユ祦绋嬬殑瀹℃壒浜哄彲鏌ョ湅涓氬姟璇︽儏銆� */
+    private boolean isProcessParticipant(String processInstanceId, Long userId) {
+        if (processInstanceId == null || userId == null) {
+            return false;
+        }
+        if (bpmTaskService.getTodoTask(userId, null, processInstanceId) != null) {
+            return true;
+        }
+        return bpmTaskService.getTaskListByProcessInstanceId(processInstanceId, true).stream()
+                .anyMatch(task -> Objects.equals(NumberUtil.parseLong(task.getAssignee(), null), userId)
+                        || Objects.equals(NumberUtil.parseLong(task.getOwner(), null), userId));
+    }
+
+    private HrmOvertimeApplicationRespVO buildResp(HrmOvertimeApplicationDO application) {
+        HrmOvertimeApplicationRespVO resp = BeanUtils.toBean(application, HrmOvertimeApplicationRespVO.class);
+        HrmEmployeeDO employee = employeeMapper.selectById(application.getEmployeeId());
+        if (employee != null) fillEmployee(resp, employee);
+        resp.setOvertimeTypeName(HrmOvertimeTypeEnum.getName(application.getOvertimeType()));
+        resp.setStatusName(HrmAuditStatusEnum.getNameByStatus(application.getStatus()));
+        return resp;
+    }
+
+    private void fillEmployee(HrmOvertimeApplicationRespVO resp, HrmEmployeeDO employee) {
+        resp.setUserId(employee.getUserId()); resp.setEmployeeId(employee.getId());
+        resp.setEmployeeName(employee.getName()); resp.setEmployeeNo(employee.getEmployeeNo());
+        resp.setDeptId(employee.getDeptId());
+        DeptRespDTO dept = employee.getDeptId() == null ? null : deptApi.getDept(employee.getDeptId());
+        PostRespDTO post = employee.getPostId() == null ? null
+                : postApi.getPostMap(Collections.singleton(employee.getPostId())).get(employee.getPostId());
+        resp.setDeptName(dept == null ? null : dept.getName());
+        resp.setPostName(post == null ? null : post.getName());
+    }
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmOvertimeApplicationStatusListener.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmOvertimeApplicationStatusListener.java
new file mode 100644
index 0000000..cabb0c6
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/approval/listener/HrmOvertimeApplicationStatusListener.java
@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.hrm.service.approval.listener;
+
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEvent;
+import cn.iocoder.yudao.module.bpm.api.event.BpmProcessInstanceStatusEventListener;
+import cn.iocoder.yudao.module.hrm.enums.HrmAuditStatusEnum;
+import cn.iocoder.yudao.module.hrm.service.approval.HrmOvertimeApplicationService;
+import cn.iocoder.yudao.module.hrm.util.HrmAuditStatusUtils;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Component;
+
+@Component
+public class HrmOvertimeApplicationStatusListener extends BpmProcessInstanceStatusEventListener {
+    @Resource private HrmOvertimeApplicationService service;
+    @Override protected String getProcessDefinitionKey() { return HrmOvertimeApplicationService.BPM_PROCESS_DEFINITION_KEY; }
+    @Override protected void onEvent(BpmProcessInstanceStatusEvent event) {
+        Integer status = HrmAuditStatusUtils.convertBpmResultToAuditStatus(event.getStatus());
+        if (status == null) status = HrmAuditStatusEnum.PROCESS.getStatus();
+        service.updateAuditStatus(Long.valueOf(event.getBusinessKey()), status);
+    }
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
index 352854b..25cfd62 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordService.java
@@ -37,6 +37,12 @@
      */
     HrmAttendanceRecordDO getAttendanceRecordByUserIdAndDate(Long userId, LocalDate date);
 
+    /** 鍒涘缓瀵煎叆鐨勮�冨嫟璁板綍 */
+    void createImportedRecord(HrmAttendanceRecordDO record);
+
+    /** 鏇存柊瀵煎叆鐨勮�冨嫟璁板綍 */
+    void updateImportedRecord(HrmAttendanceRecordDO record);
+
     /**
      * 鎵撳崱锛堜笂鐝墦鍗★級
      *
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
index f7ff44c..60ba60d 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/attendance/HrmAttendanceRecordServiceImpl.java
@@ -67,6 +67,86 @@
     }
 
     @Override
+    public void createImportedRecord(HrmAttendanceRecordDO record) {
+        // 鎸夎�冨嫟瑙勫垯閲嶇畻鎵撳崱绫诲瀷涓庢椂闀匡紝涓嶄俊浠� Excel 濉啓鐨勫��
+        fillClockTypes(record);
+        fillCalculatedHours(record);
+        attendanceRecordMapper.insert(record);
+    }
+
+    @Override
+    public void updateImportedRecord(HrmAttendanceRecordDO record) {
+        fillClockTypes(record);
+        fillCalculatedHours(record);
+        attendanceRecordMapper.updateById(record);
+    }
+
+    /**
+     * 鎸夎�冨嫟瑙勫垯閲嶇畻涓婁笅鐝墦鍗$被鍨嬶紙姝e父/杩熷埌/鏃╅��锛夛紝涓庢墦鍗¢�昏緫淇濇寔涓�鑷�
+     */
+    private void fillClockTypes(HrmAttendanceRecordDO record) {
+        if (record.getDeptId() == null) {
+            return;
+        }
+        HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId());
+        if (rule == null) {
+            return;
+        }
+        // 涓婄彮鎵撳崱绫诲瀷锛氭櫄浜� 涓婄彮鏃堕棿+杩熷埌鍏佽 璁颁负杩熷埌
+        if (record.getClockInTime() != null && rule.getWorkStartTime() != null) {
+            int lateAllowMinutes = rule.getLateAllowMinutes() != null ? rule.getLateAllowMinutes() : 0;
+            LocalTime lateThreshold = rule.getWorkStartTime().plusMinutes(lateAllowMinutes);
+            LocalTime clockInOnly = record.getClockInTime().toLocalTime();
+            record.setClockInType(clockInOnly.isAfter(lateThreshold)
+                    ? HrmAttendanceClockTypeEnum.LATE.getType()
+                    : HrmAttendanceClockTypeEnum.NORMAL.getType());
+        }
+        // 涓嬬彮鎵撳崱绫诲瀷锛氭棭浜� 涓嬬彮鏃堕棿-鏃╅��鍏佽 璁颁负鏃╅��
+        if (record.getClockOutTime() != null && rule.getWorkEndTime() != null) {
+            int earlyAllowMinutes = rule.getEarlyAllowMinutes() != null ? rule.getEarlyAllowMinutes() : 0;
+            LocalTime earlyThreshold = rule.getWorkEndTime().minusMinutes(earlyAllowMinutes);
+            LocalTime clockOutOnly = record.getClockOutTime().toLocalTime();
+            record.setClockOutType(clockOutOnly.isBefore(earlyThreshold)
+                    ? HrmAttendanceClockTypeEnum.EARLY.getType()
+                    : HrmAttendanceClockTypeEnum.NORMAL.getType());
+        }
+    }
+
+    /**
+     * 鏍规嵁涓婁笅鐝墦鍗℃椂闂村拰鑰冨嫟瑙勫垯锛岃绠楀伐浣滄椂闀夸笌鍔犵彮鏃堕暱
+     */
+    private void fillCalculatedHours(HrmAttendanceRecordDO record) {
+        // 宸ヤ綔鏃堕暱 = 涓嬬彮鎵撳崱鏃堕棿 - 涓婄彮鎵撳崱鏃堕棿锛堜笌鎵撳崱閫昏緫涓�鑷达級
+        if (record.getClockInTime() != null && record.getClockOutTime() != null) {
+            Duration duration = Duration.between(record.getClockInTime(), record.getClockOutTime());
+            BigDecimal workHours = BigDecimal.valueOf(duration.toMinutes())
+                    .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+            record.setWorkHours(workHours);
+        }
+        // 鍔犵彮鏃堕暱锛氭寜鑰冨嫟瑙勫垯鐨勬爣鍑嗕笅鐝椂闂磋绠楄秴棰濋儴鍒�
+        if (record.getClockOutTime() != null && record.getDeptId() != null) {
+            HrmAttendanceRuleDO rule = attendanceRuleService.getAttendanceRuleByDeptId(record.getDeptId());
+            if (rule != null && rule.getWorkEndTime() != null) {
+                LocalTime workEnd = rule.getWorkEndTime();
+                LocalDate outDate = record.getClockOutTime().toLocalDate();
+                LocalDateTime workEndDateTime = LocalDateTime.of(outDate, workEnd);
+                if (record.getClockOutTime().isAfter(workEndDateTime)) {
+                    Duration overtimeDuration = Duration.between(workEndDateTime, record.getClockOutTime());
+                    BigDecimal overtimeThreshold = rule.getOvertimeThreshold() != null
+                            ? rule.getOvertimeThreshold() : BigDecimal.ZERO;
+                    BigDecimal overtimeHours = BigDecimal.valueOf(overtimeDuration.toMinutes())
+                            .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP);
+                    record.setOvertimeHours(overtimeHours.compareTo(overtimeThreshold) > 0 ? overtimeHours : BigDecimal.ZERO);
+                } else {
+                    record.setOvertimeHours(BigDecimal.ZERO);
+                }
+            } else {
+                record.setOvertimeHours(BigDecimal.ZERO);
+            }
+        }
+    }
+
+    @Override
     @Transactional(rollbackFor = Exception.class)
     public HrmAttendanceRecordDO clockIn(Long userId, LocalDateTime clockTime, String location) {
         LocalDate clockDate = clockTime.toLocalDate();
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
index 6ece4cb..90424ba 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeService.java
@@ -99,6 +99,12 @@
      */
     HrmEmployeeDO getEmployeeByUserId(Long userId);
 
+    /** 鏍规嵁鍛樺伐濮撳悕鑾峰彇鍛樺伐 */
+    HrmEmployeeDO getEmployeeByName(String name);
+
+    /** 鏍规嵁鍛樺伐濮撳悕鑾峰彇鎵�鏈夊悓鍚嶅憳宸� */
+    List<HrmEmployeeDO> getEmployeeListByName(String name);
+
     /**
      * 瀵煎叆鍛樺伐鍒楄〃
      *
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
index 02e3c20..c538b8c 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java
@@ -335,6 +335,16 @@
     }
 
     @Override
+    public HrmEmployeeDO getEmployeeByName(String name) {
+        return employeeMapper.selectByName(name);
+    }
+
+    @Override
+    public List<HrmEmployeeDO> getEmployeeListByName(String name) {
+        return employeeMapper.selectListByName(name);
+    }
+
+    @Override
     public HrmEmployeeImportRespVO importEmployeeList(List<HrmEmployeeImportExcelVO> importEmployees,
                                                       HrmEmployeeImportReqVO importReqVO) {
         // 1. 鏍¢獙闈炵┖
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java
index c73c853..fbcfda7 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigService.java
@@ -22,8 +22,18 @@
     HrmLeaveTypeConfigDO getLeaveTypeConfig(Integer leaveType);
 
     /**
+     * 鏂板璇峰亣绫诲瀷閰嶇疆
+     */
+    void createLeaveTypeConfig(HrmLeaveTypeConfigDO config);
+
+    /**
      * 鏇存柊璇峰亣绫诲瀷閰嶇疆
      */
     void updateLeaveTypeConfig(HrmLeaveTypeConfigDO config);
 
+    /**
+     * 鍒犻櫎璇峰亣绫诲瀷閰嶇疆
+     */
+    void deleteLeaveTypeConfig(Long id);
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java
index 4374c44..0b48e62 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmLeaveTypeConfigServiceImpl.java
@@ -10,6 +10,7 @@
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.LEAVE_TYPE_CONFIG_NOT_EXISTS;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.LEAVE_TYPE_CONFIG_TYPE_EXISTS;
 
 /**
  * 璇峰亣绫诲瀷鎵f閰嶇疆 Service 瀹炵幇绫�
@@ -34,11 +35,26 @@
     }
 
     @Override
+    public void createLeaveTypeConfig(HrmLeaveTypeConfigDO config) {
+        // 鏍¢獙鍚屼竴璇峰亣绫诲瀷鍙厑璁稿瓨鍦ㄤ竴鏉¢厤缃�
+        if (leaveTypeConfigMapper.selectCount(HrmLeaveTypeConfigDO::getLeaveType, config.getLeaveType()) > 0) {
+            throw exception(LEAVE_TYPE_CONFIG_TYPE_EXISTS);
+        }
+        leaveTypeConfigMapper.insert(config);
+    }
+
+    @Override
     public void updateLeaveTypeConfig(HrmLeaveTypeConfigDO config) {
         validateLeaveTypeConfigExists(config.getId());
         leaveTypeConfigMapper.updateById(config);
     }
 
+    @Override
+    public void deleteLeaveTypeConfig(Long id) {
+        validateLeaveTypeConfigExists(id);
+        leaveTypeConfigMapper.deleteById(id);
+    }
+
     private void validateLeaveTypeConfigExists(Long id) {
         if (leaveTypeConfigMapper.selectById(id) == null) {
             throw exception(LEAVE_TYPE_CONFIG_NOT_EXISTS);
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
index 8279b4e..8be05ab 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java
@@ -5,6 +5,7 @@
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSalaryCalculationPageReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmLeaveApplicationDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.approval.HrmOvertimeApplicationDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.employee.HrmEmployeeDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSalaryDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmEmployeeSocialSecurityDO;
@@ -13,7 +14,9 @@
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDetailDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryPaymentDO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
 import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmLeaveApplicationMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.approval.HrmOvertimeApplicationMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSocialSecurityMapper;
@@ -22,8 +25,10 @@
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentDetailMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryPaymentMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
 import cn.iocoder.yudao.module.hrm.dal.redis.HrmNoRedisDAO;
 import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.hrm.enums.HrmOvertimeTypeEnum;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -82,6 +87,10 @@
     @Resource
     private HrmLeaveApplicationMapper leaveApplicationMapper;
     @Resource
+    private HrmOvertimeApplicationMapper overtimeApplicationMapper;
+    @Resource
+    private HrmSalaryStructureMapper salaryStructureMapper;
+    @Resource
     private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
     @Resource
     private HrmSalaryPaymentMapper salaryPaymentMapper;
@@ -130,7 +139,10 @@
                 if (existCalculations.size() > 1) {
                     throw exception(SALARY_CALCULATION_DUPLICATE, employee.getName(), period);
                 }
-                continue; // 宸插瓨鍦ㄥ垯璺宠繃
+                // 宸茬‘璁ゆ垨宸插彂鏀剧殑璁板綍涓嶅彲鑷姩鏀瑰啓锛涘緟纭璁板綍鍏佽閲嶇畻锛屼互鍚告敹鍚庢潵瀹℃壒閫氳繃鐨勫姞鐝��
+                if (existCalculations.get(0).getStatus() != 0) {
+                    continue;
+                }
             }
 
             // 2.2 鑾峰彇鍛樺伐钖叕妗f锛堟寜鍛樺伐ID鏌ヨ锛�
@@ -151,15 +163,20 @@
             // 2.5 鏋勫缓鏍哥畻璁板綍锛堝寘鍚墸闄よ绠楋紝閫氳繃鏂规鑾峰彇姣斾緥锛�
             HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
 
-            // 2.6 鐢熸垚鏍哥畻鍗曞彿
-            String no = noRedisDAO.generateSalaryCalculationNo();
-            while (salaryCalculationMapper.selectByNo(no) != null) {
-                no = noRedisDAO.generateSalaryCalculationNo();
+            // 2.6 淇濆瓨锛氶娆$敓鎴愬崟鍙凤紱宸叉湁寰呯‘璁よ褰曞垯淇濈暀鍗曞彿骞惰鐩栭噸绠楃粨鏋�
+            if (CollUtil.isNotEmpty(existCalculations)) {
+                HrmSalaryCalculationDO existing = existCalculations.get(0);
+                calculation.setId(existing.getId());
+                calculation.setNo(existing.getNo());
+                salaryCalculationMapper.updateById(calculation);
+            } else {
+                String no = noRedisDAO.generateSalaryCalculationNo();
+                while (salaryCalculationMapper.selectByNo(no) != null) {
+                    no = noRedisDAO.generateSalaryCalculationNo();
+                }
+                calculation.setNo(no);
+                salaryCalculationMapper.insert(calculation);
             }
-            calculation.setNo(no);
-
-            // 2.7 淇濆瓨鏍哥畻璁板綍
-            salaryCalculationMapper.insert(calculation);
         }
     }
 
@@ -174,13 +191,14 @@
     }
 
     /**
-     * 鏋勫缓榛樿鐨勫憳宸ヨ柂閰。妗堬紙鍩轰簬鍛樺伐琛ㄧ殑鍩烘湰宸ヨ祫锛�
+     * 鏋勫缓榛樿鐨勫憳宸ヨ柂閰。妗堛�傚熀鏈伐璧勭粺涓�鍙栧憳宸ユ。妗堬紝缁╂晥宸ヨ祫榛樿涓嶈嚜鍔ㄧ敓鎴愩��
      */
     private HrmEmployeeSalaryDO buildDefaultEmployeeSalary(Long employeeId, HrmEmployeeDO employee) {
+        BigDecimal baseSalary = employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO;
         return HrmEmployeeSalaryDO.builder()
                 .userId(employeeId) // 浣跨敤鍛樺伐ID
                 .salaryStructureId(employee.getSalaryStructureId())
-                .baseSalary(employee.getBaseSalary() != null ? employee.getBaseSalary() : BigDecimal.ZERO)
+                .baseSalary(baseSalary)
                 .performanceSalary(BigDecimal.ZERO)
                 .mealAllowance(BigDecimal.ZERO)
                 .transportAllowance(BigDecimal.ZERO)
@@ -204,17 +222,18 @@
         BigDecimal mealAllowance = employeeSalary.getMealAllowance() != null ? employeeSalary.getMealAllowance() : BigDecimal.ZERO;
         BigDecimal transportAllowance = employeeSalary.getTransportAllowance() != null ? employeeSalary.getTransportAllowance() : BigDecimal.ZERO;
         BigDecimal otherAllowance = employeeSalary.getOtherAllowance() != null ? employeeSalary.getOtherAllowance() : BigDecimal.ZERO;
-        BigDecimal overtimePay = BigDecimal.ZERO;
+        OvertimeSalary overtimeSalary = calculateOvertimeSalary(employee, employeeSalary, period, baseSalary);
+        BigDecimal overtimePay = overtimeSalary.pay();
 
         // 搴斿彂宸ヨ祫鎬婚
         BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
                 .add(transportAllowance).add(otherAllowance).add(overtimePay);
 
-        // 鑾峰彇绀句繚鍩烘暟锛堝憳宸ユ。妗堜腑鐨勪釜鎬у寲鍩烘暟锛�
+        // 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鍛樺伐鍩烘湰宸ヨ祫
         BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
                 ? socialSecurity.getSocialSecurityBase() : baseSalary;
 
-        // 鑾峰彇鍏Н閲戝熀鏁帮紙鍛樺伐妗f涓殑涓�у寲鍩烘暟锛�
+        // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鍛樺伐鍩烘湰宸ヨ祫
         BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
                 ? socialSecurity.getHousingFundBase() : baseSalary;
 
@@ -277,7 +296,7 @@
                 .leaveDeduction(leaveDeduction)
                 .actualSalary(actualSalary)
                 .workDays(22) // 榛樿宸ヤ綔鏃�
-                .overtimeHours(BigDecimal.ZERO)
+                .overtimeHours(overtimeSalary.hours())
                 .status(0) // 寰呯‘璁�
                 .build();
     }
@@ -333,6 +352,54 @@
 
         return totalLeaveDeduction;
     }
+
+    /**
+     * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇枫�傚皬鏃跺伐璧勬寜鍩烘湰宸ヨ祫 / 21.75 / 8 璁$畻锛�
+     * 宸ヤ綔鏃ラ粯璁� 1.5 鍊嶃�佷紤鎭棩榛樿 2 鍊嶃�佹硶瀹氳妭鍋囨棩榛樿 3 鍊嶏紝鍧囧彲鐢辫柂閰粨鏋勮鐩栥��
+     */
+    private OvertimeSalary calculateOvertimeSalary(HrmEmployeeDO employee, HrmEmployeeSalaryDO employeeSalary,
+                                                    String period, BigDecimal baseSalary) {
+        if (employee.getUserId() == null) {
+            return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+        }
+        YearMonth month = YearMonth.parse(period);
+        List<HrmOvertimeApplicationDO> applications = overtimeApplicationMapper.selectApprovedByUserAndDateRange(
+                employee.getUserId(), month.atDay(1), month.atEndOfMonth());
+        if (CollUtil.isEmpty(applications)) {
+            return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+        }
+        Long structureId = employeeSalary.getSalaryStructureId() != null
+                ? employeeSalary.getSalaryStructureId() : employee.getSalaryStructureId();
+        HrmSalaryStructureDO structure = structureId == null ? null : salaryStructureMapper.selectById(structureId);
+        BigDecimal workdayMultiplier = structure != null && structure.getOvertimePayRatio() != null
+                ? structure.getOvertimePayRatio() : HrmOvertimeTypeEnum.WORKDAY.getSalaryMultiplier();
+        BigDecimal restDayMultiplier = structure != null && structure.getRestDayOvertimePayRatio() != null
+                ? structure.getRestDayOvertimePayRatio() : HrmOvertimeTypeEnum.REST_DAY.getSalaryMultiplier();
+        BigDecimal legalHolidayMultiplier = structure != null && structure.getLegalHolidayOvertimePayRatio() != null
+                ? structure.getLegalHolidayOvertimePayRatio() : HrmOvertimeTypeEnum.LEGAL_HOLIDAY.getSalaryMultiplier();
+        BigDecimal hourlySalary = baseSalary.divide(new BigDecimal("21.75"), 8, RoundingMode.HALF_UP)
+                .divide(new BigDecimal("8"), 8, RoundingMode.HALF_UP);
+        BigDecimal totalHours = BigDecimal.ZERO;
+        BigDecimal totalPay = BigDecimal.ZERO;
+        for (HrmOvertimeApplicationDO application : applications) {
+            BigDecimal hours = application.getDuration() == null ? BigDecimal.ZERO : application.getDuration();
+            HrmOvertimeTypeEnum type = HrmOvertimeTypeEnum.valueOfType(application.getOvertimeType());
+            if (type == null || hours.signum() <= 0) {
+                continue;
+            }
+            BigDecimal multiplier = switch (type) {
+                case WORKDAY -> workdayMultiplier;
+                case REST_DAY -> restDayMultiplier;
+                case LEGAL_HOLIDAY -> legalHolidayMultiplier;
+            };
+            totalHours = totalHours.add(hours);
+            totalPay = totalPay.add(hourlySalary.multiply(hours).multiply(multiplier));
+        }
+        return new OvertimeSalary(totalHours.setScale(1, RoundingMode.HALF_UP),
+                totalPay.setScale(2, RoundingMode.HALF_UP));
+    }
+
+    private record OvertimeSalary(BigDecimal hours, BigDecimal pay) {}
 
     @Override
     @Transactional(rollbackFor = Exception.class)
@@ -577,4 +644,4 @@
         salaryCalculationMapper.deleteById(id);
     }
 
-}
\ No newline at end of file
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java
new file mode 100644
index 0000000..d8a848e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureService.java
@@ -0,0 +1,19 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+
+import java.util.List;
+
+public interface HrmSalaryStructureService {
+
+    Long createSalaryStructure(HrmSalaryStructureDO structure);
+
+    void updateSalaryStructure(HrmSalaryStructureDO structure);
+
+    void deleteSalaryStructure(Long id);
+
+    HrmSalaryStructureDO getSalaryStructure(Long id);
+
+    List<HrmSalaryStructureDO> getSalaryStructureList(String name, Integer status);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java
new file mode 100644
index 0000000..5659c58
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryStructureServiceImpl.java
@@ -0,0 +1,79 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSalaryStructureDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.employee.HrmEmployeeMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmEmployeeSalaryMapper;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSalaryStructureMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+import org.springframework.validation.annotation.Validated;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_STRUCTURE_IN_USE;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_STRUCTURE_NAME_EXISTS;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SALARY_STRUCTURE_NOT_EXISTS;
+
+@Service
+@Validated
+public class HrmSalaryStructureServiceImpl implements HrmSalaryStructureService {
+
+    @Resource
+    private HrmSalaryStructureMapper salaryStructureMapper;
+    @Resource
+    private HrmEmployeeMapper employeeMapper;
+    @Resource
+    private HrmEmployeeSalaryMapper employeeSalaryMapper;
+
+    @Override
+    public Long createSalaryStructure(HrmSalaryStructureDO structure) {
+        validateNameUnique(null, structure.getName());
+        // 鍩烘湰宸ヨ祫鐢卞憳宸ユ。妗堢淮鎶ゃ�傛暟鎹簱鍘嗗彶瀛楁闈炵┖锛屾柊寤虹粨鏋勭粺涓�鍐欏叆鍗犱綅鍊� 0銆�
+        structure.setBaseSalary(BigDecimal.ZERO);
+        salaryStructureMapper.insert(structure);
+        return structure.getId();
+    }
+
+    @Override
+    public void updateSalaryStructure(HrmSalaryStructureDO structure) {
+        validateExists(structure.getId());
+        validateNameUnique(structure.getId(), structure.getName());
+        salaryStructureMapper.updateById(structure);
+    }
+
+    @Override
+    public void deleteSalaryStructure(Long id) {
+        validateExists(id);
+        if (employeeMapper.selectCountBySalaryStructureId(id) > 0
+                || employeeSalaryMapper.selectCountBySalaryStructureId(id) > 0) {
+            throw exception(SALARY_STRUCTURE_IN_USE);
+        }
+        salaryStructureMapper.deleteById(id);
+    }
+
+    @Override
+    public HrmSalaryStructureDO getSalaryStructure(Long id) {
+        return salaryStructureMapper.selectById(id);
+    }
+
+    @Override
+    public List<HrmSalaryStructureDO> getSalaryStructureList(String name, Integer status) {
+        return salaryStructureMapper.selectListByCondition(name, status);
+    }
+
+    private void validateExists(Long id) {
+        if (id == null || salaryStructureMapper.selectById(id) == null) {
+            throw exception(SALARY_STRUCTURE_NOT_EXISTS);
+        }
+    }
+
+    private void validateNameUnique(Long id, String name) {
+        HrmSalaryStructureDO structure = salaryStructureMapper.selectByName(name);
+        if (structure != null && !structure.getId().equals(id)) {
+            throw exception(SALARY_STRUCTURE_NAME_EXISTS);
+        }
+    }
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
index 664126c..97e1853 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/MdmItemController.java
@@ -3,6 +3,9 @@
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
@@ -22,13 +25,17 @@
 import cn.iocoder.yudao.module.mes.api.wm.warehouse.dto.MesWmWarehouseRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 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.util.*;
 import java.util.stream.Collectors;
 
@@ -268,4 +275,26 @@
         }
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱瀵煎叆鐗╂枡妯℃澘")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<MdmItemImportExcelVO> list = Arrays.asList(
+                MdmItemImportExcelVO.builder().code("ITEM001").name("绀轰緥鐗╂枡").specification("瑙勬牸").status(0).isBatchManaged(false).build()
+        );
+        ExcelUtils.write(response, "鐗╂枡瀵煎叆妯℃澘.xls", "鐗╂枡", MdmItemImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆鐗╂枡")
+    @Parameters({
+            @Parameter(name = "file", description = "Excel 鏂囦欢", required = true),
+            @Parameter(name = "updateSupport", description = "鏄惁鏀寔鏇存柊锛岄粯璁や负 false", example = "true")
+    })
+    @PreAuthorize("@ss.hasPermission('mdm:item:import')")
+    public CommonResult<MdmItemImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
+                                                         @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
+        List<MdmItemImportExcelVO> list = ExcelUtils.read(file, MdmItemImportExcelVO.class);
+        return success(itemService.importItemList(list, updateSupport));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java
new file mode 100644
index 0000000..9fa5c81
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportExcelVO.java
@@ -0,0 +1,86 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 鐗╂枡瀵煎叆 Excel VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MdmItemImportExcelVO {
+
+    @ExcelProperty("鐗╂枡缂栫爜")
+    private String code;
+
+    @ExcelProperty("鐗╂枡鍚嶇О")
+    private String name;
+
+    @ExcelProperty("鏉$爜")
+    private String barCode;
+
+    @ExcelProperty("瑙勬牸鍨嬪彿")
+    private String specification;
+
+    @ExcelProperty("鐗╂枡鍒嗙被缂栫爜")
+    private String categoryCode;
+
+    @ExcelProperty("璁¢噺鍗曚綅缂栫爜")
+    private String unitMeasureCode;
+
+    @ExcelProperty("杈呭崟浣�1缂栫爜")
+    private String unitMeasureCode2;
+
+    @ExcelProperty("杈呭崟浣�1鎹㈢畻姣旂巼")
+    private BigDecimal unitMeasureRate1;
+
+    @ExcelProperty("杈呭崟浣�2缂栫爜")
+    private String unitMeasureCode3;
+
+    @ExcelProperty("杈呭崟浣�2鎹㈢畻姣旂巼")
+    private BigDecimal unitMeasureRate2;
+
+    @ExcelProperty("榛樿浠撳簱缂栫爜")
+    private String warehouseCode;
+
+    @ExcelProperty("閲囪喘浠�")
+    private BigDecimal purchasePrice;
+
+    @ExcelProperty("閿�鍞环")
+    private BigDecimal salesPrice;
+
+    @ExcelProperty("鎴愭湰浠�")
+    private BigDecimal costPrice;
+
+    @ExcelProperty("瀹夊叏搴撳瓨")
+    private BigDecimal safetyStock;
+
+    @ExcelProperty("鏈�浣庡簱瀛�")
+    private BigDecimal minStock;
+
+    @ExcelProperty("鏈�楂樺簱瀛�")
+    private BigDecimal maxStock;
+
+    @ExcelProperty("鏄惁鎵规绠$悊")
+    private Boolean isBatchManaged;
+
+    @ExcelProperty("淇濊川鏈�(澶�)")
+    private Integer expiryDay;
+
+    @ExcelProperty("鐘舵��")
+    private Integer status;
+
+    @ExcelProperty("鏄惁鍚屾鍒癕ES")
+    private Boolean syncMes;
+
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportRespVO.java
new file mode 100644
index 0000000..f77601d
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/item/vo/MdmItemImportRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.item.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 鐗╂枡瀵煎叆 Response VO")
+@Data
+@Builder
+public class MdmItemImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勭墿鏂欐暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勭墿鏂欐暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触鐨勭墿鏂欓泦鍚� key 涓虹墿鏂欏悕锛寁alue 涓哄け璐ュ師鍥�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, String> failureList;
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
index 87c4dd1..072e4d0 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/MdmUnitMeasureController.java
@@ -3,19 +3,27 @@
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
 import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 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.util.Arrays;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -93,4 +101,27 @@
         return success(unitMeasureService.getUnitMeasureList(ids));
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱瀵煎叆璁¢噺鍗曚綅妯℃澘")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<MdmUnitMeasureImportExcelVO> list = Arrays.asList(
+                MdmUnitMeasureImportExcelVO.builder().code("PC").name("鐗�").primaryFlag(true).changeRate(new java.math.BigDecimal("1")).status(0).remark("绀轰緥锛氫富鍗曚綅").build(),
+                MdmUnitMeasureImportExcelVO.builder().code("XIA").name("绠�").primaryFlag(false).primaryCode("PC").changeRate(new java.math.BigDecimal("100")).status(0).remark("绀轰緥锛�1绠�=100鐗�").build()
+        );
+        ExcelUtils.write(response, "璁¢噺鍗曚綅瀵煎叆妯℃澘.xls", "璁¢噺鍗曚綅", MdmUnitMeasureImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆璁¢噺鍗曚綅")
+    @Parameters({
+            @Parameter(name = "file", description = "Excel 鏂囦欢", required = true),
+            @Parameter(name = "updateSupport", description = "鏄惁鏀寔鏇存柊锛岄粯璁や负 false", example = "true")
+    })
+    @PreAuthorize("@ss.hasPermission('mdm:unit-measure:import')")
+    public CommonResult<MdmUnitMeasureImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
+                                                                @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
+        List<MdmUnitMeasureImportExcelVO> list = ExcelUtils.read(file, MdmUnitMeasureImportExcelVO.class);
+        return success(unitMeasureService.importUnitMeasureList(list, updateSupport));
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportExcelVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportExcelVO.java
new file mode 100644
index 0000000..6216134
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportExcelVO.java
@@ -0,0 +1,45 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.unit.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.system.enums.DictTypeConstants;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.math.BigDecimal;
+
+/**
+ * MDM 璁¢噺鍗曚綅瀵煎叆 Excel VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MdmUnitMeasureImportExcelVO {
+
+    @ExcelProperty("鍗曚綅缂栫爜")
+    private String code;
+
+    @ExcelProperty("鍗曚綅鍚嶇О")
+    private String name;
+
+    @ExcelProperty("鏄惁涓诲崟浣�")
+    private Boolean primaryFlag;
+
+    @ExcelProperty("涓诲崟浣嶇紪鐮�")
+    private String primaryCode;
+
+    @ExcelProperty("涓庝富鍗曚綅鎹㈢畻姣斾緥")
+    private BigDecimal changeRate;
+
+    @ExcelProperty(value = "鐘舵��", converter = DictConvert.class)
+    @DictFormat(DictTypeConstants.COMMON_STATUS)
+    private Integer status;
+
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportRespVO.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportRespVO.java
new file mode 100644
index 0000000..e33b673
--- /dev/null
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/controller/admin/unit/vo/MdmUnitMeasureImportRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mdm.controller.admin.unit.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "绠$悊鍚庡彴 - MDM 璁¢噺鍗曚綅瀵煎叆 Response VO")
+@Data
+@Builder
+public class MdmUnitMeasureImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勫崟浣嶆暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勫崟浣嶆暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触鐨勫崟浣嶉泦鍚� key 涓哄崟浣嶅悕锛寁alue 涓哄け璐ュ師鍥�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, String> failureList;
+
+}
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
index 747fc12..a3df052 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/enums/ErrorCodeConstants.java
@@ -17,6 +17,7 @@
     ErrorCode MDM_ITEM_SKU_IS_DISABLE = new ErrorCode(1_050_005, "鐗╂枡SKU宸茬鐢�");
     ErrorCode MDM_ITEM_SKU_CODE_DUPLICATE = new ErrorCode(1_050_006, "SKU缂栫爜宸插瓨鍦�");
     ErrorCode MDM_ITEM_EXISTS_REFERENCE = new ErrorCode(1_050_007, "鐗╂枡宸茶涓氬姟寮曠敤锛屾棤娉曞垹闄�");
+    ErrorCode MDM_ITEM_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_008, "瀵煎叆鐨勭墿鏂欐暟鎹笉鑳戒负绌�");
 
     // ========== 鐗╂枡鍒嗙被鐩稿叧 1-050-051 ~ 1-050-100 ==========
     ErrorCode MDM_ITEM_CATEGORY_NOT_EXISTS = new ErrorCode(1_050_051, "鐗╂枡鍒嗙被涓嶅瓨鍦�");
@@ -31,6 +32,7 @@
     ErrorCode MDM_UNIT_MEASURE_CODE_DUPLICATE = new ErrorCode(1_050_102, "璁¢噺鍗曚綅缂栫爜宸插瓨鍦�");
     ErrorCode MDM_UNIT_MEASURE_NAME_DUPLICATE = new ErrorCode(1_050_103, "璁¢噺鍗曚綅鍚嶇О宸插瓨鍦�");
     ErrorCode MDM_UNIT_MEASURE_EXISTS_ITEM = new ErrorCode(1_050_104, "璁¢噺鍗曚綅琚墿鏂欎娇鐢紝鏃犳硶鍒犻櫎");
+    ErrorCode MDM_UNIT_MEASURE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_050_105, "瀵煎叆鐨勮閲忓崟浣嶆暟鎹笉鑳戒负绌�");
 
     // ========== 鍝佺墝鐩稿叧 1-050-151 ~ 1-050-200 ==========
     ErrorCode MDM_BRAND_NOT_EXISTS = new ErrorCode(1_050_151, "鍝佺墝涓嶅瓨鍦�");
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
index 3ade231..aee19e7 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemService.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.mdm.service.item;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
@@ -153,4 +155,13 @@
      */
     List<MdmItemDO> getItemSimpleList();
 
+    /**
+     * 瀵煎叆鐗╂枡
+     *
+     * @param importList      瀵煎叆鍒楄〃
+     * @param isUpdateSupport 鏄惁鏀寔鏇存柊
+     * @return 瀵煎叆缁撴灉
+     */
+    MdmItemImportRespVO importItemList(List<MdmItemImportExcelVO> importList, Boolean isUpdateSupport);
+
 }
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
index 138b8e0..d71bea1 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/item/MdmItemServiceImpl.java
@@ -2,18 +2,23 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+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.object.BeanUtils;
 import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemPageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.item.vo.MdmItemSaveReqVO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.category.MdmItemCategoryDO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemBatchConfigDO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.item.MdmItemDO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
+import cn.iocoder.yudao.module.mdm.dal.mysql.category.MdmItemCategoryMapper;
 import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemBatchConfigMapper;
 import cn.iocoder.yudao.module.mdm.dal.mysql.item.MdmItemMapper;
+import cn.iocoder.yudao.module.mdm.dal.mysql.unit.MdmUnitMeasureMapper;
 import cn.iocoder.yudao.module.mdm.service.brand.MdmBrandService;
 import cn.iocoder.yudao.module.mdm.service.category.MdmItemCategoryService;
 import cn.iocoder.yudao.module.mdm.service.unit.MdmUnitMeasureService;
@@ -60,6 +65,12 @@
     private MesMdItemApi mesMdItemApi;
     @Resource
     private MdmItemBatchConfigMapper itemBatchConfigMapper;
+
+    @Resource
+    private MdmItemCategoryMapper itemCategoryMapper;
+
+    @Resource
+    private MdmUnitMeasureMapper unitMeasureMapper;
 
     @Resource
     private MesMdItemTypeApi mesMdItemTypeApi;
@@ -422,4 +433,121 @@
         }
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MdmItemImportRespVO importItemList(List<MdmItemImportExcelVO> importList, Boolean isUpdateSupport) {
+        if (CollUtil.isEmpty(importList)) {
+            throw exception(MDM_ITEM_IMPORT_LIST_IS_EMPTY);
+        }
+        MdmItemImportRespVO respVO = MdmItemImportRespVO.builder()
+                .createList(new ArrayList<>()).updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>()).build();
+
+        // 1. 棰勭儹锛氬垎绫荤紪鐮� -> ID銆佸崟浣嶇紪鐮� -> ID
+        Map<String, Long> categoryCodeIdMap = new HashMap<>();
+        for (MdmItemCategoryDO category : itemCategoryMapper.selectList()) {
+            if (category.getCode() != null) {
+                categoryCodeIdMap.put(category.getCode(), category.getId());
+            }
+        }
+        Map<String, Long> unitCodeIdMap = new HashMap<>();
+        for (MdmUnitMeasureDO unit : unitMeasureMapper.selectList()) {
+            unitCodeIdMap.put(unit.getCode(), unit.getId());
+        }
+
+        // 2. 閬嶅巻瀵煎叆
+        for (MdmItemImportExcelVO importVO : importList) {
+            // 2.1 鍩虹鏍¢獙
+            if (StrUtil.isBlank(importVO.getCode()) || StrUtil.isBlank(importVO.getName())) {
+                respVO.getFailureList().put(StrUtil.blankToDefault(importVO.getName(), importVO.getCode()), "鐗╂枡缂栫爜鍜岀墿鏂欏悕绉颁笉鑳戒负绌�");
+                continue;
+            }
+            // 2.2 瑙f瀽鍒嗙被
+            Long categoryId = null;
+            if (StrUtil.isNotBlank(importVO.getCategoryCode())) {
+                categoryId = categoryCodeIdMap.get(importVO.getCategoryCode());
+                if (categoryId == null) {
+                    respVO.getFailureList().put(importVO.getName(), "鐗╂枡鍒嗙被缂栫爜涓嶅瓨鍦細" + importVO.getCategoryCode());
+                    continue;
+                }
+            }
+            // 2.3 瑙f瀽涓诲崟浣�
+            Long unitMeasureId = null;
+            if (StrUtil.isNotBlank(importVO.getUnitMeasureCode())) {
+                unitMeasureId = unitCodeIdMap.get(importVO.getUnitMeasureCode());
+                if (unitMeasureId == null) {
+                    respVO.getFailureList().put(importVO.getName(), "璁¢噺鍗曚綅缂栫爜涓嶅瓨鍦細" + importVO.getUnitMeasureCode());
+                    continue;
+                }
+            }
+            // 2.4 瑙f瀽杈呭崟浣�1
+            Long unitMeasureId2 = null;
+            if (StrUtil.isNotBlank(importVO.getUnitMeasureCode2())) {
+                unitMeasureId2 = unitCodeIdMap.get(importVO.getUnitMeasureCode2());
+                if (unitMeasureId2 == null) {
+                    respVO.getFailureList().put(importVO.getName(), "杈呭崟浣�1缂栫爜涓嶅瓨鍦細" + importVO.getUnitMeasureCode2());
+                    continue;
+                }
+            }
+            // 2.5 瑙f瀽杈呭崟浣�2
+            Long unitMeasureId3 = null;
+            if (StrUtil.isNotBlank(importVO.getUnitMeasureCode3())) {
+                unitMeasureId3 = unitCodeIdMap.get(importVO.getUnitMeasureCode3());
+                if (unitMeasureId3 == null) {
+                    respVO.getFailureList().put(importVO.getName(), "杈呭崟浣�2缂栫爜涓嶅瓨鍦細" + importVO.getUnitMeasureCode3());
+                    continue;
+                }
+            }
+
+            // 2.6 缁勮 SaveReqVO锛堝鐢� create/update 閫昏緫锛�
+            MdmItemSaveReqVO saveReqVO = new MdmItemSaveReqVO();
+            saveReqVO.setCode(importVO.getCode());
+            saveReqVO.setName(importVO.getName());
+            saveReqVO.setBarCode(importVO.getBarCode());
+            saveReqVO.setSpecification(importVO.getSpecification());
+            saveReqVO.setCategoryId(categoryId);
+            saveReqVO.setUnitMeasureId(unitMeasureId);
+            saveReqVO.setUnitMeasureId2(unitMeasureId2);
+            saveReqVO.setUnitMeasureRate1(importVO.getUnitMeasureRate1());
+            saveReqVO.setUnitMeasureId3(unitMeasureId3);
+            saveReqVO.setUnitMeasureRate2(importVO.getUnitMeasureRate2());
+            saveReqVO.setPurchasePrice(importVO.getPurchasePrice());
+            saveReqVO.setSalesPrice(importVO.getSalesPrice());
+            saveReqVO.setCostPrice(importVO.getCostPrice());
+            saveReqVO.setSafetyStock(importVO.getSafetyStock());
+            saveReqVO.setMinStock(importVO.getMinStock());
+            saveReqVO.setMaxStock(importVO.getMaxStock());
+            saveReqVO.setIsBatchManaged(importVO.getIsBatchManaged());
+            saveReqVO.setExpiryDay(importVO.getExpiryDay());
+            saveReqVO.setStatus(importVO.getStatus() != null ? importVO.getStatus() : cn.iocoder.yudao.framework.common.enums.CommonStatusEnum.ENABLE.getStatus());
+            saveReqVO.setSyncMes(importVO.getSyncMes() != null ? importVO.getSyncMes() : false);
+            saveReqVO.setRemark(importVO.getRemark());
+            saveReqVO.setItemType(1); // 榛樿鐗╂枡绫诲瀷锛氱墿鏂�
+
+            // 2.7 宸插瓨鍦紙鎸夌紪鐮佸垽鏂級
+            MdmItemDO existByCode = itemMapper.selectByCode(importVO.getCode());
+            try {
+                if (existByCode != null) {
+                    if (!Boolean.TRUE.equals(isUpdateSupport)) {
+                        respVO.getFailureList().put(importVO.getName(), "鐗╂枡缂栫爜宸插瓨鍦�");
+                        continue;
+                    }
+                    saveReqVO.setId(existByCode.getId());
+                    updateItem(saveReqVO);
+                    respVO.getUpdateList().add(importVO.getName());
+                } else {
+                    createItem(saveReqVO);
+                    respVO.getCreateList().add(importVO.getName());
+                }
+            } catch (Exception ex) {
+                // 鍗曟潯澶辫触锛岃褰曞苟缁х画
+                String reason = ex instanceof cn.iocoder.yudao.framework.common.exception.ServiceException
+                        ? ((cn.iocoder.yudao.framework.common.exception.ServiceException) ex).getMessage()
+                        : ex.getMessage();
+                respVO.getFailureList().put(importVO.getName(), reason == null ? "瀵煎叆澶辫触" : reason);
+            }
+        }
+        return respVO;
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java
index 1170228..db48241 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureService.java
@@ -1,6 +1,8 @@
 package cn.iocoder.yudao.module.mdm.service.unit;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
@@ -99,4 +101,13 @@
      */
     MdmUnitMeasureDO getUnitMeasureByCode(String code);
 
+    /**
+     * 瀵煎叆璁¢噺鍗曚綅
+     *
+     * @param importList      瀵煎叆鍒楄〃
+     * @param isUpdateSupport 鏄惁鏀寔鏇存柊
+     * @return 瀵煎叆缁撴灉
+     */
+    MdmUnitMeasureImportRespVO importUnitMeasureList(List<MdmUnitMeasureImportExcelVO> importList, Boolean isUpdateSupport);
+
 }
diff --git a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java
index 3b393d3..8660a6a 100644
--- a/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java
+++ b/yudao-module-mdm/src/main/java/cn/iocoder/yudao/module/mdm/service/unit/MdmUnitMeasureServiceImpl.java
@@ -2,8 +2,11 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportExcelVO;
+import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureImportRespVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasurePageReqVO;
 import cn.iocoder.yudao.module.mdm.controller.admin.unit.vo.MdmUnitMeasureSaveReqVO;
 import cn.iocoder.yudao.module.mdm.dal.dataobject.unit.MdmUnitMeasureDO;
@@ -125,4 +128,73 @@
         return unitMeasureMapper.selectByCode(code);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public MdmUnitMeasureImportRespVO importUnitMeasureList(List<MdmUnitMeasureImportExcelVO> importList, Boolean isUpdateSupport) {
+        if (CollUtil.isEmpty(importList)) {
+            throw exception(MDM_UNIT_MEASURE_IMPORT_LIST_IS_EMPTY);
+        }
+        MdmUnitMeasureImportRespVO respVO = MdmUnitMeasureImportRespVO.builder()
+                .createList(new ArrayList<>()).updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>()).build();
+
+        // 1. 寤虹珛缂栫爜 -> ID 鏄犲皠锛堝厛鍔犺浇鐜版湁鍗曚綅锛屽啀鍙犲姞鏈鏂板缓鐨勫崟浣嶏級锛岀敤浜庤В鏋�"涓诲崟浣嶇紪鐮�"鍒�
+        Map<String, Long> codeIdMap = new HashMap<>();
+        for (MdmUnitMeasureDO unit : unitMeasureMapper.selectList()) {
+            codeIdMap.put(unit.getCode(), unit.getId());
+        }
+        // 璁板綍鏈瀵煎叆宸插鐞嗚繃鐨勭紪鐮侊紝閬垮厤鍚屼竴鏂囦欢鍐呭悗缁鎵句笉鍒板垰瀵煎叆鐨勪富鍗曚綅
+        Map<String, Long> importedCodeIdMap = new HashMap<>();
+
+        // 2. 閬嶅巻瀵煎叆
+        for (MdmUnitMeasureImportExcelVO importVO : importList) {
+            // 2.1 鍩虹鏍¢獙
+            if (StrUtil.isBlank(importVO.getCode()) || StrUtil.isBlank(importVO.getName())) {
+                respVO.getFailureList().put(StrUtil.blankToDefault(importVO.getName(), importVO.getCode()), "鍗曚綅缂栫爜鍜屽崟浣嶅悕绉颁笉鑳戒负绌�");
+                continue;
+            }
+            // 2.2 瑙f瀽涓诲崟浣� ID锛堥潪涓诲崟浣嶆椂蹇呭~涓诲崟浣嶇紪鐮侊級
+            Long primaryId = null;
+            if (StrUtil.isNotBlank(importVO.getPrimaryCode())) {
+                primaryId = importedCodeIdMap.get(importVO.getPrimaryCode()) != null
+                        ? importedCodeIdMap.get(importVO.getPrimaryCode())
+                        : codeIdMap.get(importVO.getPrimaryCode());
+                if (primaryId == null) {
+                    respVO.getFailureList().put(importVO.getName(), "涓诲崟浣嶇紪鐮佷笉瀛樺湪锛�" + importVO.getPrimaryCode());
+                    continue;
+                }
+            }
+
+            // 2.3 宸插瓨鍦紙鎸夌紪鐮佸垽鏂級
+            MdmUnitMeasureDO existByCode = unitMeasureMapper.selectByCode(importVO.getCode());
+            if (existByCode != null) {
+                if (!Boolean.TRUE.equals(isUpdateSupport)) {
+                    respVO.getFailureList().put(importVO.getName(), "鍗曚綅缂栫爜宸插瓨鍦�");
+                    continue;
+                }
+                // 鏀寔鏇存柊锛氫互缂栫爜涓哄噯鏇存柊
+                MdmUnitMeasureDO updateObj = BeanUtils.toBean(importVO, MdmUnitMeasureDO.class);
+                updateObj.setId(existByCode.getId());
+                updateObj.setPrimaryId(primaryId);
+                unitMeasureMapper.updateById(updateObj);
+                respVO.getUpdateList().add(importVO.getName());
+            } else {
+                // 2.4 鏍¢獙鍚嶇О鍞竴锛堟柊澧炲満鏅級
+                MdmUnitMeasureDO existByName = unitMeasureMapper.selectByName(importVO.getName());
+                if (existByName != null) {
+                    respVO.getFailureList().put(importVO.getName(), "鍗曚綅鍚嶇О宸插瓨鍦�");
+                    continue;
+                }
+                // 2.5 鏂板
+                MdmUnitMeasureDO newObj = BeanUtils.toBean(importVO, MdmUnitMeasureDO.class);
+                newObj.setPrimaryId(primaryId);
+                unitMeasureMapper.insert(newObj);
+                respVO.getCreateList().add(importVO.getName());
+                // 璁板綍鏈鏂板缓鐨勭紪鐮侊紝渚涘悓鏂囦欢鍚庣画琛屽紩鐢�
+                importedCodeIdMap.put(importVO.getCode(), newObj.getId());
+            }
+        }
+        return respVO;
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemTypeController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemTypeController.java
index 3e0a69d..bd91d3f 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemTypeController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/MesMdItemTypeController.java
@@ -3,6 +3,9 @@
 import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeSaveReqVO;
@@ -10,13 +13,18 @@
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemTypeService;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.Parameters;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import jakarta.annotation.Resource;
+import jakarta.servlet.http.HttpServletResponse;
 import jakarta.validation.Valid;
 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.util.Arrays;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@@ -83,4 +91,27 @@
                 .setItemOrProduct(itemType.getItemOrProduct())));
     }
 
+    @GetMapping("/get-import-template")
+    @Operation(summary = "鑾峰緱瀵煎叆鐗╂枡浜у搧鍒嗙被妯℃澘")
+    public void importTemplate(HttpServletResponse response) throws IOException {
+        List<MesMdItemTypeImportExcelVO> list = Arrays.asList(
+                MesMdItemTypeImportExcelVO.builder().code("RAW").name("鍘熸潗鏂�").itemOrProduct("ITEM").sort(1).status(0).remark("绀轰緥锛氶《绾у垎绫�").build(),
+                MesMdItemTypeImportExcelVO.builder().code("METAL").name("閲戝睘鏉愭枡").parentCode("RAW").itemOrProduct("ITEM").sort(1).status(0).remark("绀轰緥锛氱埗鍒嗙被缂栫爜涓� RAW").build()
+        );
+        ExcelUtils.write(response, "鐗╂枡鍒嗙被瀵煎叆妯℃澘.xls", "鐗╂枡鍒嗙被", MesMdItemTypeImportExcelVO.class, list);
+    }
+
+    @PostMapping("/import")
+    @Operation(summary = "瀵煎叆鐗╂枡浜у搧鍒嗙被")
+    @Parameters({
+            @Parameter(name = "file", description = "Excel 鏂囦欢", required = true),
+            @Parameter(name = "updateSupport", description = "鏄惁鏀寔鏇存柊锛岄粯璁や负 false", example = "true")
+    })
+    @PreAuthorize("@ss.hasPermission('mes:md-item-type:import')")
+    public CommonResult<MesMdItemTypeImportRespVO> importExcel(@RequestParam("file") MultipartFile file,
+                                                               @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception {
+        List<MesMdItemTypeImportExcelVO> list = ExcelUtils.read(file, MesMdItemTypeImportExcelVO.class);
+        return success(itemTypeService.importItemTypeList(list, updateSupport));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportExcelVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportExcelVO.java
new file mode 100644
index 0000000..a4c4ce2
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportExcelVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type;
+
+import cn.idev.excel.annotation.ExcelProperty;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+/**
+ * MES 鐗╂枡浜у搧鍒嗙被瀵煎叆 Excel VO
+ */
+@Data
+@Builder
+@AllArgsConstructor
+@NoArgsConstructor
+public class MesMdItemTypeImportExcelVO {
+
+    @ExcelProperty("鍒嗙被缂栫爜")
+    private String code;
+
+    @ExcelProperty("鍒嗙被鍚嶇О")
+    private String name;
+
+    @ExcelProperty("鐖跺垎绫荤紪鐮�")
+    private String parentCode;
+
+    @ExcelProperty("鐗╂枡/浜у搧鏍囪瘑")
+    private String itemOrProduct;
+
+    @ExcelProperty("鏄剧ず鎺掑簭")
+    private Integer sort;
+
+    @ExcelProperty("鐘舵��")
+    private Integer status;
+
+    @ExcelProperty("澶囨敞")
+    private String remark;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportRespVO.java
new file mode 100644
index 0000000..d5c99c7
--- /dev/null
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/md/item/vo/type/MesMdItemTypeImportRespVO.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Builder;
+import lombok.Data;
+
+import java.util.List;
+import java.util.Map;
+
+@Schema(description = "绠$悊鍚庡彴 - MES 鐗╂枡浜у搧鍒嗙被瀵煎叆 Response VO")
+@Data
+@Builder
+public class MesMdItemTypeImportRespVO {
+
+    @Schema(description = "鍒涘缓鎴愬姛鐨勫垎绫绘暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> createList;
+
+    @Schema(description = "鏇存柊鎴愬姛鐨勫垎绫绘暟缁�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private List<String> updateList;
+
+    @Schema(description = "瀵煎叆澶辫触鐨勫垎绫婚泦鍚� key 涓哄垎绫诲悕锛寁alue 涓哄け璐ュ師鍥�", requiredMode = Schema.RequiredMode.REQUIRED)
+    private Map<String, String> failureList;
+
+}
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
index d5ef60b..26d88c8 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/MesPdDocumentController.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.mes.controller.admin.pd.document;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.apilog.core.annotation.ApiAccessLog;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
@@ -14,6 +15,7 @@
 import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
 import cn.iocoder.yudao.module.mes.service.pd.document.MesPdDocumentService;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
@@ -117,10 +119,22 @@
         // 1.1 鎵归噺鑾峰彇鍏宠仈鏁版嵁锛堜笂浼犱汉锛�
         Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(list, MesPdDocumentDO::getUploadUserId));
         // 2. 鎷兼帴 VO
-        return BeanUtils.toBean(list, MesPdDocumentRespVO.class, vo -> {
+        List<MesPdDocumentRespVO> voList = BeanUtils.toBean(list, MesPdDocumentRespVO.class, vo -> {
             MapUtils.findAndThen(userMap, vo.getUploadUserId(),
                     user -> vo.setUploadUserNickname(user.getNickname()));
         });
+        // 3. 鎷兼帴鏂囦欢淇℃伅锛氫紭鍏堝彇绗竴涓檮浠剁殑鍦板潃浣滀负 fileUrl锛屽苟鍦ㄦ湭濉枃浠跺悕鏃惰嚜鍔ㄨˉ鍏�
+        for (MesPdDocumentRespVO vo : voList) {
+            List<StorageBlobRespDTO> attachments = storageAttachmentApi.listAttachments("mes_pd_document", vo.getId());
+            if (CollUtil.isNotEmpty(attachments)) {
+                StorageBlobRespDTO first = attachments.get(0);
+                vo.setFileUrl(first.getUrl());
+                if (StrUtil.isEmpty(vo.getFileName())) {
+                    vo.setFileName(first.getName());
+                }
+            }
+        }
+        return voList;
     }
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
index f462b80..5defe5e 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/pd/document/vo/MesPdDocumentRespVO.java
@@ -41,6 +41,9 @@
     @ExcelProperty("鏂囦欢鍚�")
     private String fileName;
 
+    @Schema(description = "鏂囦欢鍦板潃", example = "https://www.iocoder.cn/design_v1.pdf")
+    private String fileUrl;
+
     @Schema(description = "涓婁紶浜�", example = "1")
     private Long uploadUserId;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
index 8cbe7ec..24e5253 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/MesWmReturnVendorController.java
@@ -146,6 +146,14 @@
         return success(returnVendorService.checkReturnVendorQuantity(id));
     }
 
+    @PostMapping("/import-purchase-order-items")
+    @Operation(summary = "鏍规嵁鍏宠仈閲囪喘璁㈠崟甯﹀叆鐗╂枡鏄庣粏")
+    @Parameter(name = "id", description = "缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('mes:wm-return-vendor:update')")
+    public CommonResult<Integer> importPurchaseOrderItems(@RequestParam("id") Long id) {
+        return success(returnVendorService.importPurchaseOrderItems(id));
+    }
+
     // ==================== 鎷兼帴 VO ====================
 
     private List<MesWmReturnVendorRespVO> buildRespVOList(List<MesWmReturnVendorDO> list) {
@@ -158,8 +166,10 @@
                 .collect(java.util.stream.Collectors.toMap(SrmSupplierRespDTO::getId, s -> s));
         // 2. 鏋勫缓缁撴灉
         return BeanUtils.toBean(list, MesWmReturnVendorRespVO.class, vo ->
-                MapUtils.findAndThen(vendorMap, vo.getVendorId(), vendor ->
-                        vo.setVendorName(vendor.getName())));
+                MapUtils.findAndThen(vendorMap, vo.getVendorId(), vendor -> {
+                    vo.setVendorName(vendor.getName());
+                    vo.setVendorCode(vendor.getCode());
+                }));
     }
 
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
index e6f0263..63797fd 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorRespVO.java
@@ -18,6 +18,9 @@
     @Schema(description = "閫�璐у崟鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "渚涘簲鍟嗛��璐�")
     private String name;
 
+    @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "1")
+    private Long purchaseOrderId;
+
     @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "PO20250226001")
     private String purchaseOrderCode;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
index 720348a..8932b98 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/controller/admin/wm/returnvendor/vo/MesWmReturnVendorSaveReqVO.java
@@ -22,6 +22,9 @@
     @NotBlank(message = "閫�璐у崟鍚嶇О涓嶈兘涓虹┖")
     private String name;
 
+    @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "1")
+    private Long purchaseOrderId;
+
     @Schema(description = "閲囪喘璁㈠崟缂栧彿", example = "PO20250226001")
     private String purchaseOrderCode;
 
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
index 9b8b4be..02dba0b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/dataobject/wm/returnvendor/MesWmReturnVendorDO.java
@@ -38,6 +38,12 @@
     private String name;
     /**
      * 閲囪喘璁㈠崟缂栧彿
+     *
+     * 鍏宠仈 ERP 閲囪喘璁㈠崟 {@link cn.iocoder.yudao.module.erp.dal.dataobject.purchase.ErpPurchaseOrderDO#getId()}
+     */
+    private Long purchaseOrderId;
+    /**
+     * 閲囪喘璁㈠崟缂栫爜锛堝啑浣欏瓧娈碉紝鐢ㄤ簬鏄剧ず锛�
      */
     private String purchaseOrderCode;
     /**
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 a4078de..1583a20 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
@@ -42,4 +42,10 @@
                 .le(MesDvMeteringDO::getNextCheckDate, end));
     }
 
+    default List<MesDvMeteringDO> selectListByNextCheckDateBefore(LocalDate date) {
+        return selectList(new LambdaQueryWrapperX<MesDvMeteringDO>()
+                .isNotNull(MesDvMeteringDO::getNextCheckDate)
+                .lt(MesDvMeteringDO::getNextCheckDate, date));
+    }
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
index ca7c0fe..74929ab 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/RedisKeyConstants.java
@@ -31,4 +31,14 @@
      */
     String METERING_EXPIRE_REMIND_SCAN = "mes:dv:metering:remind:scan:";
 
+    /**
+     * 璁¢噺鍣ㄥ叿宸茶繃鏈熸彁閱掑幓閲�
+     *
+     * KEY 鏍煎紡锛歮es:dv:metering:overdue:remind:{meteringId}:{nextCheckDate}
+     * VALUE 鏁版嵁鏍煎紡锛�1
+     *
+     * 涓� {@link #METERING_EXPIRE_REMIND} 鐩镐簰鐙珛锛屼繚璇佸櫒鍏疯繃鏈熷悗浠嶈兘鍗曠嫭鎻愰啋涓�娆�
+     */
+    String METERING_EXPIRE_REMIND_OVERDUE = "mes:dv:metering:overdue:remind:";
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java
index a12e6f7..5424456 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/dal/redis/dv/metering/MesDvMeteringRemindRedisDAO.java
@@ -41,6 +41,20 @@
     }
 
     /**
+     * 灏濊瘯鏍囪宸叉彁閱掞紙宸茶繃鏈熷満鏅紝60 澶╁唴鍙彁閱掍竴娆★級
+     *
+     * 浣跨敤鐙珛鐨� KEY锛屼笌 {@link #tryMarkReminded} 鐩镐簰闅旂锛屼繚璇佸櫒鍏疯繃鏈熷悗浠嶈兘鍗曠嫭鎻愰啋涓�娆�
+     *
+     * @param meteringId 璁¢噺鍣ㄥ叿缂栧彿
+     * @param nextCheckDate 鍒版湡鏃�
+     * @return true 琛ㄧず棣栨鏍囪鎴愬姛锛岄渶瑕佸彂閫佹彁閱�
+     */
+    public boolean tryMarkOverdueReminded(Long meteringId, LocalDate nextCheckDate) {
+        String key = RedisKeyConstants.METERING_EXPIRE_REMIND_OVERDUE + meteringId + ":" + nextCheckDate;
+        return Boolean.TRUE.equals(stringRedisTemplate.opsForValue().setIfAbsent(key, "1", REMIND_TIMEOUT));
+    }
+
+    /**
      * 灏濊瘯鑾峰彇褰撳ぉ鐨勬壂鎻忛棬闂╋紝閬垮厤澶氬疄渚嬪苟鍙戦噸澶嶆壂鎻�
      *
      * @param today 褰撳ぉ鏃ユ湡
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
index 3b683cc..cadf846 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/enums/ErrorCodeConstants.java
@@ -18,6 +18,7 @@
     ErrorCode MD_ITEM_TYPE_CODE_DUPLICATE = new ErrorCode(1_040_100_005, "鍚屼竴鐖跺垎绫讳笅宸插瓨鍦ㄨ缂栫爜鐨勫垎绫�");
     ErrorCode MD_ITEM_TYPE_PARENT_IS_CHILD = new ErrorCode(1_040_100_006, "涓嶈兘璁剧疆鑷繁鐨勫瓙鍒嗙被涓虹埗鍒嗙被");
     ErrorCode MD_ITEM_TYPE_EXITS_ITEM = new ErrorCode(1_040_100_007, "璇ュ垎绫讳笅瀛樺湪鐗╂枡锛屾棤娉曞垹闄�");
+    ErrorCode MD_ITEM_TYPE_IMPORT_LIST_IS_EMPTY = new ErrorCode(1_040_100_008, "瀵煎叆鐨勭墿鏂欏垎绫绘暟鎹笉鑳戒负绌�");
 
     // ========== MES 鍩虹鏁版嵁-璁¢噺鍗曚綅锛�1-040-101-000锛� ==========
     ErrorCode MD_UNIT_MEASURE_NOT_EXISTS = new ErrorCode(1_040_101_000, "璁¢噺鍗曚綅涓嶅瓨鍦�");
@@ -616,6 +617,8 @@
     ErrorCode WM_RETURN_VENDOR_DETAIL_NOT_EXISTS = new ErrorCode(1_040_711_200, "渚涘簲鍟嗛��璐у崟鏄庣粏涓嶅瓨鍦�");
     ErrorCode WM_RETURN_VENDOR_DETAIL_QUANTITY_INVALID = new ErrorCode(1_040_711_201, "閫�璐ф槑缁嗘暟閲忓繀椤诲ぇ浜� 0");
     ErrorCode WM_RETURN_VENDOR_DETAIL_LINE_NOT_MATCH = new ErrorCode(1_040_711_202, "鎷h揣鏄庣粏涓嶅睘浜庢寚瀹氱殑渚涘簲鍟嗛��璐у崟");
+    ErrorCode WM_RETURN_VENDOR_PURCHASE_ORDER_REQUIRED = new ErrorCode(1_040_711_008, "璇峰厛閫夋嫨閲囪喘璁㈠崟");
+    ErrorCode WM_RETURN_VENDOR_PURCHASE_ORDER_NO_ITEM = new ErrorCode(1_040_711_009, "閲囪喘璁㈠崟鏃犳槑缁嗘暟鎹紝鏃犳硶甯﹀叆鐗╂枡");
 
     // ========== MES 浠撳簱绠$悊-浜у搧鏀惰揣鍗曪紙1-040-712-000锛� ==========
     ErrorCode WM_PRODUCT_RECPT_NOT_EXISTS = new ErrorCode(1_040_712_000, "浜у搧鏀惰揣鍗曚笉瀛樺湪");
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java
index 2c8416c..90a0cb1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/dv/metering/MesDvMeteringRemindServiceImpl.java
@@ -34,9 +34,13 @@
      */
     private static final int DEFAULT_REMIND_DAYS = 30;
     /**
-     * 鍒版湡鎻愰啋绔欏唴淇℃ā鏉跨紪鍙�
+     * 鍗冲皢鍒版湡鎻愰啋绔欏唴淇℃ā鏉跨紪鍙�
      */
     private static final String REMIND_TEMPLATE_CODE = "mes_metering_expire_remind";
+    /**
+     * 宸茶繃鏈熸彁閱掔珯鍐呬俊妯℃澘缂栧彿
+     */
+    private static final String OVERDUE_TEMPLATE_CODE = "mes_metering_expire_overdue";
 
     @Resource
     private MesDvMeteringMapper meteringMapper;
@@ -57,18 +61,28 @@
             }
             // 2. 璇诲彇鎻愰啋澶╂暟锛堥粯璁� 30锛�
             int remindDays = getRemindDays();
-            // 3. 鏌ヨ [today, today + remindDays] 鍒版湡鐨勫櫒鍏�
-            List<MesDvMeteringDO> list = meteringMapper.selectListByNextCheckDateBetween(
+            // 3. 鏌ヨ宸茶繃鏈燂紙鍒版湡鏃� < 浠婂ぉ锛変笌鍗冲皢鍒版湡锛堝埌鏈熸棩鍦� [today, today + remindDays]锛夌殑鍣ㄥ叿
+            List<MesDvMeteringDO> overdueList = meteringMapper.selectListByNextCheckDateBefore(today);
+            List<MesDvMeteringDO> dueList = meteringMapper.selectListByNextCheckDateBetween(
                     today, today.plusDays(remindDays));
-            // 4. 閫愬彴鍙戦�佺珯鍐呬俊锛堟寜鍣ㄥ叿 + 鍒版湡鏃ュ幓閲嶏級
-            for (MesDvMeteringDO metering : list) {
+            // 4. 閫愬彴鍙戦�佺珯鍐呬俊锛堟寜鍣ㄥ叿 + 鍒版湡鏃ュ幓閲嶏紝涓ょ被鐩镐簰鐙珛锛�
+            for (MesDvMeteringDO metering : overdueList) {
+                if (metering.getChargeUserId() == null || metering.getNextCheckDate() == null) {
+                    continue;
+                }
+                if (!meteringRemindRedisDAO.tryMarkOverdueReminded(metering.getId(), metering.getNextCheckDate())) {
+                    continue;
+                }
+                sendRemindMessage(metering, OVERDUE_TEMPLATE_CODE);
+            }
+            for (MesDvMeteringDO metering : dueList) {
                 if (metering.getChargeUserId() == null || metering.getNextCheckDate() == null) {
                     continue;
                 }
                 if (!meteringRemindRedisDAO.tryMarkReminded(metering.getId(), metering.getNextCheckDate())) {
                     continue;
                 }
-                sendRemindMessage(metering);
+                sendRemindMessage(metering, REMIND_TEMPLATE_CODE);
             }
         } catch (Exception ex) {
             // best-effort锛氭彁閱掑け璐ヤ笉褰卞搷鍒楄〃鏌ヨ
@@ -89,14 +103,14 @@
         }
     }
 
-    private void sendRemindMessage(MesDvMeteringDO metering) {
+    private void sendRemindMessage(MesDvMeteringDO metering, String templateCode) {
         Map<String, Object> params = new HashMap<>();
         params.put("meteringCode", metering.getCode());
         params.put("meteringName", metering.getName());
         params.put("nextCheckDate", metering.getNextCheckDate().toString());
         NotifySendSingleToUserReqDTO reqDTO = new NotifySendSingleToUserReqDTO()
                 .setUserId(metering.getChargeUserId())
-                .setTemplateCode(REMIND_TEMPLATE_CODE)
+                .setTemplateCode(templateCode)
                 .setTemplateParams(params);
         notifyMessageSendApi.sendSingleMessageToAdmin(reqDTO);
     }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
index e6864f0..8ed799b 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeService.java
@@ -1,5 +1,7 @@
 package cn.iocoder.yudao.module.mes.service.md.item;
 
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
@@ -90,4 +92,13 @@
      */
     List<MesMdItemTypeDO> getItemTypeChildrenList(Long parentId);
 
+    /**
+     * 瀵煎叆鐗╂枡浜у搧鍒嗙被
+     *
+     * @param importList      瀵煎叆鍒楄〃
+     * @param isUpdateSupport 鏄惁鏀寔鏇存柊
+     * @return 瀵煎叆缁撴灉
+     */
+    MesMdItemTypeImportRespVO importItemTypeList(List<MesMdItemTypeImportExcelVO> importList, Boolean isUpdateSupport);
+
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
index ab5b7a2..9d51f7c 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/md/item/MesMdItemTypeServiceImpl.java
@@ -2,10 +2,13 @@
 
 import cn.hutool.core.collection.CollUtil;
 import cn.hutool.core.util.ObjUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.mdm.api.category.MdmItemCategoryApi;
 import cn.iocoder.yudao.module.mdm.api.category.dto.MdmItemCategoryRespDTO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportExcelVO;
+import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeImportRespVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeListReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.md.item.vo.type.MesMdItemTypeSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.md.item.MesMdItemTypeDO;
@@ -230,4 +233,77 @@
         return mesDO;
     }
 
+    @Override
+    @org.springframework.transaction.annotation.Transactional(rollbackFor = Exception.class)
+    public MesMdItemTypeImportRespVO importItemTypeList(List<MesMdItemTypeImportExcelVO> importList, Boolean isUpdateSupport) {
+        if (CollUtil.isEmpty(importList)) {
+            throw exception(MD_ITEM_TYPE_IMPORT_LIST_IS_EMPTY);
+        }
+        MesMdItemTypeImportRespVO respVO = MesMdItemTypeImportRespVO.builder()
+                .createList(new ArrayList<>()).updateList(new ArrayList<>())
+                .failureList(new LinkedHashMap<>()).build();
+
+        // 1. 寤虹珛缂栫爜 -> ID 鏄犲皠锛堢幇鏈夊垎绫� + 鏈鏂板缓鍒嗙被锛夛紝鐢ㄤ簬瑙f瀽"鐖跺垎绫荤紪鐮�"鍒�
+        Map<String, Long> codeIdMap = new HashMap<>();
+        for (MesMdItemTypeDO itemType : itemTypeMapper.selectList()) {
+            codeIdMap.put(itemType.getCode(), itemType.getId());
+        }
+        Map<String, Long> importedCodeIdMap = new HashMap<>();
+
+        // 2. 閬嶅巻瀵煎叆
+        for (MesMdItemTypeImportExcelVO importVO : importList) {
+            // 2.1 鍩虹鏍¢獙
+            if (StrUtil.isBlank(importVO.getCode()) || StrUtil.isBlank(importVO.getName())) {
+                respVO.getFailureList().put(StrUtil.blankToDefault(importVO.getName(), importVO.getCode()), "鍒嗙被缂栫爜鍜屽垎绫诲悕绉颁笉鑳戒负绌�");
+                continue;
+            }
+            // 2.2 瑙f瀽鐖跺垎绫� ID
+            Long parentId = MesMdItemTypeDO.PARENT_ID_ROOT;
+            if (StrUtil.isNotBlank(importVO.getParentCode())) {
+                Long pid = importedCodeIdMap.get(importVO.getParentCode()) != null
+                        ? importedCodeIdMap.get(importVO.getParentCode())
+                        : codeIdMap.get(importVO.getParentCode());
+                if (pid == null) {
+                    respVO.getFailureList().put(importVO.getName(), "鐖跺垎绫荤紪鐮佷笉瀛樺湪锛�" + importVO.getParentCode());
+                    continue;
+                }
+                parentId = pid;
+            }
+
+            // 2.3 宸插瓨鍦紙鎸夌紪鐮佸垽鏂級
+            MesMdItemTypeDO existByCode = itemTypeMapper.selectByCode(importVO.getCode());
+            if (existByCode != null) {
+                if (!Boolean.TRUE.equals(isUpdateSupport)) {
+                    respVO.getFailureList().put(importVO.getName(), "鍒嗙被缂栫爜宸插瓨鍦�");
+                    continue;
+                }
+                MesMdItemTypeDO updateObj = BeanUtils.toBean(importVO, MesMdItemTypeDO.class);
+                updateObj.setId(existByCode.getId());
+                updateObj.setParentId(parentId);
+                itemTypeMapper.updateById(updateObj);
+                respVO.getUpdateList().add(importVO.getName());
+            } else {
+                // 2.4 鏍¢獙鍚屼竴鐖跺垎绫讳笅鍚嶇О鍞竴
+                MesMdItemTypeDO existByName = itemTypeMapper.selectByParentIdAndName(parentId, importVO.getName());
+                if (existByName != null) {
+                    respVO.getFailureList().put(importVO.getName(), "鍚屼竴鐖跺垎绫讳笅宸插瓨鍦ㄨ鍚嶇О鐨勫垎绫�");
+                    continue;
+                }
+                // 2.5 鏍¢獙鍚屼竴鐖跺垎绫讳笅缂栫爜鍞竴
+                MesMdItemTypeDO existByParentCode = itemTypeMapper.selectByParentIdAndCode(parentId, importVO.getCode());
+                if (existByParentCode != null) {
+                    respVO.getFailureList().put(importVO.getName(), "鍚屼竴鐖跺垎绫讳笅宸插瓨鍦ㄨ缂栫爜鐨勫垎绫�");
+                    continue;
+                }
+                // 2.6 鏂板
+                MesMdItemTypeDO newObj = BeanUtils.toBean(importVO, MesMdItemTypeDO.class);
+                newObj.setParentId(parentId);
+                itemTypeMapper.insert(newObj);
+                respVO.getCreateList().add(importVO.getName());
+                importedCodeIdMap.put(importVO.getCode(), newObj.getId());
+            }
+        }
+        return respVO;
+    }
+
 }
\ No newline at end of file
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
index 8ef2a53..18f02e4 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pd/document/MesPdDocumentServiceImpl.java
@@ -1,18 +1,23 @@
 package cn.iocoder.yudao.module.mes.service.pd.document;
 
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.pd.document.vo.MesPdDocumentPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.pd.document.vo.MesPdDocumentSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.pd.document.MesPdDocumentDO;
 import cn.iocoder.yudao.module.mes.dal.mysql.pd.document.MesPdDocumentMapper;
 import cn.iocoder.yudao.module.mes.service.pd.project.MesPdProjectService;
 import cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi;
+import cn.iocoder.yudao.module.system.api.storage.dto.StorageBlobRespDTO;
 import jakarta.annotation.Resource;
 import org.springframework.context.annotation.Lazy;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import java.time.LocalDateTime;
 import java.util.List;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
@@ -42,20 +47,40 @@
         projectService.validateProjectExists(createReqVO.getProjectId());
         // 鎻掑叆
         MesPdDocumentDO document = BeanUtils.toBean(createReqVO, MesPdDocumentDO.class);
+        // 琛ュ厖涓婁紶浜恒�佷笂浼犳椂闂�
+        if (document.getUploadUserId() == null) {
+            document.setUploadUserId(SecurityFrameworkUtils.getLoginUserId());
+        }
+        if (document.getUploadTime() == null) {
+            document.setUploadTime(LocalDateTime.now());
+        }
         documentMapper.insert(document);
         storageAttachmentApi.bindAttachments("file", "mes_pd_document",
                 document.getId(), createReqVO.getBlobIds());
+        // 鏈~鍐欐枃浠跺悕鏃讹紝浠庝笂浼犻檮浠惰嚜鍔ㄨˉ鍏�
+        fillFileNameIfAbsent(document);
         return document.getId();
     }
 
     @Override
     public void updateDocument(MesPdDocumentSaveReqVO updateReqVO) {
         // 鏍¢獙瀛樺湪
-        validateDocumentExists(updateReqVO.getId());
+        MesPdDocumentDO existing = validateDocumentExists(updateReqVO.getId());
         // 鏇存柊
         MesPdDocumentDO updateObj = BeanUtils.toBean(updateReqVO, MesPdDocumentDO.class);
+        // 淇濈暀鍘熶笂浼犱汉銆佷笂浼犳椂闂达紱鍘嗗彶鏁版嵁涓虹┖鏃惰ˉ榻�
+        if (updateObj.getUploadUserId() == null) {
+            updateObj.setUploadUserId(existing.getUploadUserId() != null
+                    ? existing.getUploadUserId() : SecurityFrameworkUtils.getLoginUserId());
+        }
+        if (updateObj.getUploadTime() == null) {
+            updateObj.setUploadTime(existing.getUploadTime() != null
+                    ? existing.getUploadTime() : LocalDateTime.now());
+        }
         documentMapper.updateById(updateObj);
         storageAttachmentApi.updateAttachments("file", "mes_pd_document", updateReqVO.getId(), updateReqVO.getBlobIds());
+        // 鏈~鍐欐枃浠跺悕鏃讹紝浠庝笂浼犻檮浠惰嚜鍔ㄨˉ鍏�
+        fillFileNameIfAbsent(updateObj);
     }
 
     @Override
@@ -100,4 +125,19 @@
         return document;
     }
 
+    /**
+     * 鏈~鍐欐枃浠跺悕鏃讹紝浠庝笂浼犻檮浠惰嚜鍔ㄨˉ鍏ㄦ枃浠跺悕
+     *
+     * @param document 璁捐璧勬枡
+     */
+    private void fillFileNameIfAbsent(MesPdDocumentDO document) {
+        if (StrUtil.isNotEmpty(document.getFileName())) {
+            return;
+        }
+        List<StorageBlobRespDTO> attachments = storageAttachmentApi.listAttachments("mes_pd_document", document.getId());
+        if (CollUtil.isNotEmpty(attachments)) {
+            document.setFileName(attachments.get(0).getName());
+            documentMapper.updateById(document);
+        }
+    }
 }
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
index be43cda..1fcd8f1 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/pro/mps/MesProMpsServiceImpl.java
@@ -22,6 +22,7 @@
 import cn.iocoder.yudao.module.mes.enums.pro.MesProMpsStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProFeedbackStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderSourceTypeEnum;
+import cn.iocoder.yudao.module.mes.enums.pro.MesProWorkOrderStatusEnum;
 import cn.iocoder.yudao.module.mes.service.md.autocode.MesMdAutoCodeRecordService;
 import cn.iocoder.yudao.module.mes.service.md.item.MesMdItemService;
 import cn.iocoder.yudao.module.mes.service.pro.route.MesProRouteProductService;
@@ -282,14 +283,26 @@
             throw exception(PRO_MPS_NOT_ISSUED);
         }
 
-        // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗�
+        // 绾ц仈鍙栨秷鍏宠仈鐨勭敓浜у伐鍗曪紙璺熼殢宸ュ崟褰撳墠鐘舵�侊紝涓斾笉璇激鍚堝苟涓嬪彂鐨勫叡浜伐鍗曪級
         if (mps.getWorkOrderId() != null) {
-            try {
-                workOrderService.cancelWorkOrder(mps.getWorkOrderId());
-                log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
-            } catch (Exception e) {
-                // 宸ュ崟鍙兘宸插畬鎴愭垨宸插彇娑堬紝璁板綍鏃ュ織浣嗕笉闃绘 MPS 鍙栨秷
-                log.warn("[cancelMps] 鍙栨秷鐢熶骇宸ュ崟澶辫触锛寃orkOrderId={}锛屽師鍥狅細{}", mps.getWorkOrderId(), e.getMessage());
+            // 1. 缁熻璇ュ伐鍗曡澶氬皯鏉� MPS 寮曠敤锛氬悎骞朵笅鍙戠殑澶氭潯 MPS 鍏变韩鍚屼竴涓伐鍗�
+            Long refCount = mpsMapper.selectCount(MesProMpsDO::getWorkOrderId, mps.getWorkOrderId());
+            if (refCount != null && refCount > 1) {
+                // 鍏变韩宸ュ崟锛氬彧鍙栨秷褰撳墠 MPS锛屼笉鍔ㄥ伐鍗�
+                log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 琚� {} 鏉� MPS 鍏辩敤锛岃烦杩囩骇鑱斿彇娑堝伐鍗�",
+                        mps.getWorkOrderId(), refCount);
+            } else {
+                MesProWorkOrderDO workOrder = workOrderService.getWorkOrder(mps.getWorkOrderId());
+                if (workOrder != null
+                        && MesProWorkOrderStatusEnum.CONFIRMED.getStatus().equals(workOrder.getStatus())) {
+                    // 鍙湁"宸茬‘璁�"鐘舵�佺殑宸ュ崟鎵嶉渶瑕佺骇鑱斿彇娑�
+                    workOrderService.cancelWorkOrder(mps.getWorkOrderId());
+                    log.info("[cancelMps] 绾ц仈鍙栨秷鐢熶骇宸ュ崟锛寃orkOrderId={}", mps.getWorkOrderId());
+                } else {
+                    // 宸ュ崟宸叉槸鑽夌/宸插畬鎴�/宸插彇娑堬紝鏃犻渶绾ц仈鍙栨秷锛岀洿鎺ュ彇娑� MPS
+                    log.info("[cancelMps] 鐢熶骇宸ュ崟({}) 鐘舵�佷负 {}锛屾棤闇�绾ц仈鍙栨秷",
+                            mps.getWorkOrderId(), workOrder != null ? workOrder.getStatus() : "null");
+                }
             }
         }
 
@@ -424,6 +437,10 @@
      * 鍒ゆ柇鍗曚釜 MPS 鏄惁瀹屾垚
      */
     private boolean isMpsFinished(MesProMpsDO mps) {
+        // 宸插彇娑堢殑 MPS 瑙嗕负缁堟�侊紝涓嶅啀闃诲鐢熶骇瀹屾垚鍒ゆ柇锛堜笌 checkCanCreateSalesNotice 鐨勫彛寰勪繚鎸佷竴鑷达級
+        if (MesProMpsStatusEnum.CANCELED.getStatus().equals(mps.getStatus())) {
+            return true;
+        }
         // 1. 宸茬敓浜ф暟閲� >= 璁″垝鏁伴噺
         BigDecimal quantityProduced = ObjUtil.defaultIfNull(mps.getQuantityProduced(), BigDecimal.ZERO);
         if (quantityProduced.compareTo(mps.getQuantity()) < 0) {
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java
index 9a74087..e46cb53 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorService.java
@@ -102,6 +102,14 @@
     Long getReturnVendorCountByVendorId(Long vendorId);
 
     /**
+     * 鏍规嵁鍏宠仈鐨勯噰璐鍗曪紝甯﹀叆鐗╂枡鏄庣粏鍒伴��璐у崟琛�
+     *
+     * @param id 閫�璐у崟缂栧彿
+     * @return 甯﹀叆鐨勭墿鏂欒鏁�
+     */
+    int importPurchaseOrderItems(Long id);
+
+    /**
      * 鏍¢獙渚涘簲鍟嗛��璐у崟瀛樺湪涓斾负鑽夌鐘舵��
      *
      * @param id 缂栧彿
diff --git a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
index 37f56fb..ae6d530 100644
--- a/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
+++ b/yudao-module-mes/src/main/java/cn/iocoder/yudao/module/mes/service/wm/returnvendor/MesWmReturnVendorServiceImpl.java
@@ -8,6 +8,7 @@
 import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorPageReqVO;
 import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.MesWmReturnVendorSaveReqVO;
+import cn.iocoder.yudao.module.mes.controller.admin.wm.returnvendor.vo.line.MesWmReturnVendorLineSaveReqVO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorDetailDO;
 import cn.iocoder.yudao.module.mes.dal.dataobject.wm.returnvendor.MesWmReturnVendorLineDO;
@@ -15,6 +16,9 @@
 import cn.iocoder.yudao.module.mes.enums.MesBizTypeConstants;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmReturnVendorStatusEnum;
 import cn.iocoder.yudao.module.mes.enums.wm.MesWmTransactionTypeEnum;
+import cn.iocoder.yudao.module.erp.api.purchase.ErpPurchaseOrderApi;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderItemRespDTO;
+import cn.iocoder.yudao.module.erp.api.purchase.dto.ErpPurchaseOrderRespDTO;
 import cn.iocoder.yudao.module.srm.api.supplier.SrmSupplierApi;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.MesWmTransactionService;
 import cn.iocoder.yudao.module.mes.service.wm.transaction.dto.MesWmTransactionSaveReqDTO;
@@ -24,7 +28,9 @@
 import org.springframework.validation.annotation.Validated;
 
 import java.math.BigDecimal;
+import java.util.HashSet;
 import java.util.List;
+import java.util.Set;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@@ -48,12 +54,16 @@
     private SrmSupplierApi srmSupplierApi;
     @Resource
     private MesWmTransactionService wmTransactionService;
+    @Resource
+    private ErpPurchaseOrderApi purchaseOrderApi;
 
     @Override
     @Transactional(rollbackFor = Exception.class)
     public Long createReturnVendor(MesWmReturnVendorSaveReqVO createReqVO) {
         // 1. 鏍¢獙鏁版嵁
         validateReturnVendorSaveData(createReqVO);
+        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙�
+        fillPurchaseOrderCode(createReqVO);
 
         // 2. 鎻掑叆涓昏〃
         MesWmReturnVendorDO returnVendor = BeanUtils.toBean(createReqVO, MesWmReturnVendorDO.class);
@@ -67,10 +77,29 @@
     public void updateReturnVendor(MesWmReturnVendorSaveReqVO updateReqVO) {
         // 1. 鏍¢獙鏁版嵁
         validateReturnVendorSaveData(updateReqVO);
+        // 1.1. 鑻ラ噰璐鍗曞彿涓虹┖浣嗗叧鑱斾簡閲囪喘璁㈠崟锛屽垯浠� ERP 閲囪喘璁㈠崟琛ラ綈璁㈠崟鍙�
+        fillPurchaseOrderCode(updateReqVO);
 
         // 2. 鏇存柊涓昏〃
         MesWmReturnVendorDO updateObj = BeanUtils.toBean(updateReqVO, MesWmReturnVendorDO.class);
         returnVendorMapper.updateById(updateObj);
+    }
+
+    /**
+     * 褰撻噰璐鍗曞彿涓虹┖銆佷絾鍏宠仈浜嗛噰璐鍗曟椂锛屼粠 ERP 閲囪喘璁㈠崟琛ラ綈閲囪喘璁㈠崟鍙�
+     */
+    private void fillPurchaseOrderCode(MesWmReturnVendorSaveReqVO reqVO) {
+        if (reqVO == null || reqVO.getPurchaseOrderId() == null) {
+            return;
+        }
+        if (cn.hutool.core.util.StrUtil.isNotBlank(reqVO.getPurchaseOrderCode())) {
+            return;
+        }
+        ErpPurchaseOrderRespDTO purchaseOrder =
+                purchaseOrderApi.getPurchaseOrder(reqVO.getPurchaseOrderId()).getCheckedData();
+        if (purchaseOrder != null) {
+            reqVO.setPurchaseOrderCode(purchaseOrder.getNo());
+        }
     }
 
     @Override
@@ -240,4 +269,53 @@
         return returnVendorMapper.selectCountByVendorId(vendorId);
     }
 
+    @Override
+    @Transactional(rollbackFor = Exception.class)
+    public int importPurchaseOrderItems(Long id) {
+        // 1. 鏍¢獙閫�璐у崟瀛樺湪涓斾负鑽夌鐘舵��
+        MesWmReturnVendorDO returnVendor = validateReturnVendorExistsAndPrepare(id);
+        if (returnVendor.getPurchaseOrderId() == null) {
+            throw exception(WM_RETURN_VENDOR_PURCHASE_ORDER_REQUIRED);
+        }
+
+        // 2. 鑾峰彇閲囪喘璁㈠崟鍙婃槑缁�
+        ErpPurchaseOrderRespDTO purchaseOrder = purchaseOrderApi.getPurchaseOrder(returnVendor.getPurchaseOrderId()).getCheckedData();
+        if (purchaseOrder == null) {
+            throw exception(WM_PURCHASE_ORDER_NOT_EXISTS);
+        }
+        List<ErpPurchaseOrderItemRespDTO> items = purchaseOrderApi.getPurchaseOrderItemList(returnVendor.getPurchaseOrderId()).getCheckedData();
+        if (CollUtil.isEmpty(items)) {
+            throw exception(WM_RETURN_VENDOR_PURCHASE_ORDER_NO_ITEM);
+        }
+
+        // 3. 宸插瓨鍦ㄧ殑琛岀墿鏂欓泦鍚堬紙鎸夌墿鏂欏幓閲嶏紝閬垮厤閲嶅甯﹀叆锛�
+        Set<Long> existsItemIds = new HashSet<>();
+        List<MesWmReturnVendorLineDO> existingLines = returnVendorLineService.getReturnVendorLineListByReturnId(id);
+        if (CollUtil.isNotEmpty(existingLines)) {
+            existsItemIds.addAll(convertList(existingLines, MesWmReturnVendorLineDO::getItemId));
+        }
+
+        // 4. 閬嶅巻閲囪喘璁㈠崟鏄庣粏锛岀敓鎴愰��璐ц锛堝彲閫�鏁伴噺 = 宸插叆搴� - 宸查��璐э級
+        int importedCount = 0;
+        for (ErpPurchaseOrderItemRespDTO item : items) {
+            if (item.getProductId() == null || existsItemIds.contains(item.getProductId())) {
+                continue;
+            }
+            BigDecimal inCount = item.getInCount() != null ? item.getInCount() : BigDecimal.ZERO;
+            BigDecimal returnCount = item.getReturnCount() != null ? item.getReturnCount() : BigDecimal.ZERO;
+            BigDecimal quantity = inCount.subtract(returnCount);
+            if (quantity.compareTo(BigDecimal.ZERO) <= 0) {
+                continue;
+            }
+            MesWmReturnVendorLineSaveReqVO lineReqVO = new MesWmReturnVendorLineSaveReqVO();
+            lineReqVO.setReturnId(id);
+            lineReqVO.setItemId(item.getProductId());
+            lineReqVO.setQuantity(quantity);
+            lineReqVO.setRemark(item.getRemark());
+            returnVendorLineService.createReturnVendorLine(lineReqVO);
+            importedCount++;
+        }
+        return importedCount;
+    }
+
 }
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java
index efdcc05..c851b98 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/controller/admin/certificate/vo/SrmSupplierCertificatePageReqVO.java
@@ -12,6 +12,9 @@
 @ToString(callSuper = true)
 public class SrmSupplierCertificatePageReqVO extends PageParam {
 
+    @Schema(description = "璧勮川璇佷功鍚嶇О", example = "钀ヤ笟鎵х収")
+    private String certificateName;
+
     @Schema(description = "渚涘簲鍟咺D", example = "1")
     private Long supplierId;
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java
index 07043c5..40f7b96 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/dal/mysql/SrmSupplierCertificateMapper.java
@@ -15,6 +15,7 @@
 
     default PageResult<SrmSupplierCertificateDO> selectPage(SrmSupplierCertificatePageReqVO reqVO) {
         return selectPage(reqVO, new LambdaQueryWrapperX<SrmSupplierCertificateDO>()
+                .likeIfPresent(SrmSupplierCertificateDO::getCertificateName, reqVO.getCertificateName())
                 .eqIfPresent(SrmSupplierCertificateDO::getSupplierId, reqVO.getSupplierId())
                 .eqIfPresent(SrmSupplierCertificateDO::getCertificateType, reqVO.getCertificateType())
                 .orderByDesc(SrmSupplierCertificateDO::getId));
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
index fcfe101..3c112d2 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/enums/ErrorCodeConstants.java
@@ -13,6 +13,7 @@
     ErrorCode SUPPLIER_CODE_EXISTS = new ErrorCode(1_040_100_001, "渚涘簲鍟嗙紪鐮佸凡瀛樺湪");
     ErrorCode SUPPLIER_EXISTS_REFERENCE = new ErrorCode(1_040_100_002, "渚涘簲鍟嗚涓氬姟寮曠敤锛屾棤娉曞垹闄�");
     ErrorCode SUPPLIER_CONTACT_NOT_EXISTS = new ErrorCode(1_040_100_003, "渚涘簲鍟嗚仈绯讳汉涓嶅瓨鍦�");
+    ErrorCode SUPPLIER_NOT_ENABLE = new ErrorCode(1_040_100_004, "渚涘簲鍟嗗凡鍋滅敤锛屾棤娉曞弬涓庢姇鏍�");
 
     // ========== 渚涘簲鍟嗗噯鍏� 1_040_200_000 ==========
     ErrorCode APPLY_NOT_EXISTS = new ErrorCode(1_040_200_000, "鍑嗗叆鐢宠涓嶅瓨鍦�");
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
index c9d5858..6c4c50f 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/supplier/SrmSupplierServiceImpl.java
@@ -1,6 +1,7 @@
 package cn.iocoder.yudao.module.srm.service.supplier;
 
 import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
@@ -110,6 +111,10 @@
     @Override
     public List<SrmSupplierDO> getSupplierAllList() {
         return supplierMapper.selectList(new LambdaQueryWrapperX<SrmSupplierDO>()
+                // 杩囨护鍋滅敤渚涘簲鍟嗭紝淇濈暀鏈缃姸鎬佷笌鍚敤鐘舵�佺殑妗f渚涘簲鍟嗭紙浠呯敤浜庢嫑鎶曟爣鍦烘櫙锛�
+                .and(w -> w.isNull(SrmSupplierDO::getStatus)
+                        .or()
+                        .ne(SrmSupplierDO::getStatus, CommonStatusEnum.DISABLE.getStatus()))
                 .orderByAsc(SrmSupplierDO::getId));
     }
 
diff --git a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
index 6410946..4dd6b76 100644
--- a/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
+++ b/yudao-module-srm/src/main/java/cn/iocoder/yudao/module/srm/service/tender/SrmTenderBidServiceImpl.java
@@ -1,12 +1,14 @@
 package cn.iocoder.yudao.module.srm.service.tender;
 
+import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
 import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmSupplierQuoteSaveReqVO;
 import cn.iocoder.yudao.module.srm.controller.admin.tender.vo.SrmTenderBidSaveReqVO;
+import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierDO;
 import cn.iocoder.yudao.module.srm.dal.dataobject.SrmSupplierQuoteDO;
 import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderBidDO;
 import cn.iocoder.yudao.module.srm.dal.dataobject.SrmTenderProjectDO;
-import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierApplyMapper;
+import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmSupplierQuoteMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderBidMapper;
 import cn.iocoder.yudao.module.srm.dal.mysql.SrmTenderProjectMapper;
@@ -43,7 +45,7 @@
     private SrmTenderProjectMapper tenderProjectMapper;
 
     @Resource
-    private SrmSupplierApplyMapper supplierApplyMapper;
+    private SrmSupplierMapper supplierMapper;
 
     // ========== 鎶曟爣绠$悊 ==========
 
@@ -58,9 +60,13 @@
         if (project.getEndTime() != null && LocalDateTime.now().isAfter(project.getEndTime())) {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.TENDER_END_TIME_PASSED);
         }
-        // 鏍¢獙渚涘簲鍟嗘槸鍚﹀凡閫氳繃鍑嗗叆瀹℃壒
-        if (!supplierApplyMapper.existsApprovedBySupplierId(createReqVO.getSupplierId())) {
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.BID_SUPPLIER_NOT_APPROVED);
+        // 鏍¢獙鎶曟爣渚涘簲鍟嗗瓨鍦ㄤ笖鏈仠鐢紙鍑嗗叆鐘舵�佷笉闄愬埗鎶曟爣锛屼腑鏍囩敓鎴愰噰璐鍗曟椂鑷姩鍑嗗叆锛�
+        SrmSupplierDO supplier = supplierMapper.selectById(createReqVO.getSupplierId());
+        if (supplier == null) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_EXISTS);
+        }
+        if (CommonStatusEnum.isDisable(supplier.getStatus())) {
+            throw ServiceExceptionUtil.exception(ErrorCodeConstants.SUPPLIER_NOT_ENABLE, supplier.getName());
         }
         // 鏍¢獙鎶曟爣缂栧彿鏄惁閲嶅
         if (tenderBidMapper.existsByBidNo(createReqVO.getBidNo())) {
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/SystemCompanyConfigController.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/SystemCompanyConfigController.java
new file mode 100644
index 0000000..5222170
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/SystemCompanyConfigController.java
@@ -0,0 +1,59 @@
+package cn.iocoder.yudao.module.system.controller.admin.companyconfig;
+
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo.SystemCompanyConfigRespVO;
+import cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo.SystemCompanyConfigSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.companyconfig.SystemCompanyConfigDO;
+import cn.iocoder.yudao.module.system.service.companyconfig.SystemCompanyConfigService;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.annotation.Resource;
+import jakarta.annotation.security.PermitAll;
+import jakarta.validation.Valid;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.validation.annotation.Validated;
+import org.springframework.web.bind.annotation.GetMapping;
+import org.springframework.web.bind.annotation.PostMapping;
+import org.springframework.web.bind.annotation.RequestBody;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - 鍏徃鍝佺墝淇℃伅")
+@RestController
+@RequestMapping("/system/company-config")
+@Validated
+public class SystemCompanyConfigController {
+
+    @Resource
+    private SystemCompanyConfigService companyConfigService;
+
+    /**
+     * 鑾峰緱鍏徃鍝佺墝淇℃伅
+     *
+     * 鍏嶇櫥褰曞叕寮�鎺ュ彛锛屼緵鍓嶇鐧诲綍椤� / 搴旂敤鍚姩鏃惰鍙栧叕鍙稿悕绉般�乴ogo銆佺櫥褰曢〉鏍囬銆�
+     */
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱鍏徃鍝佺墝淇℃伅锛堝厤鐧诲綍鍏紑锛�")
+    @PermitAll
+    public CommonResult<SystemCompanyConfigRespVO> getCompanyConfig() {
+        SystemCompanyConfigDO config = companyConfigService.getCompanyConfig();
+        return success(BeanUtils.toBean(config, SystemCompanyConfigRespVO.class));
+    }
+
+    /**
+     * 淇濆瓨鍏徃鍝佺墝淇℃伅
+     *
+     * 鍗曡 upsert锛岄渶鐧诲綍涓旀嫢鏈� system:company-config:update 鏉冮檺銆�
+     * 鏉冮檺閫氳繃鑿滃崟銆屽叕鍙镐俊鎭厤缃� - 淇敼銆嶆巿鏉冿紙瑙� sql/mysql/system_company_config_menu.sql锛夈��
+     */
+    @PostMapping("/save")
+    @Operation(summary = "淇濆瓨鍏徃鍝佺墝淇℃伅")
+    @PreAuthorize("@ss.hasPermission('system:company-config:update')")
+    public CommonResult<Long> saveCompanyConfig(@Valid @RequestBody SystemCompanyConfigSaveReqVO reqVO) {
+        return success(companyConfigService.saveCompanyConfig(reqVO));
+    }
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigRespVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigRespVO.java
new file mode 100644
index 0000000..7faff1b
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigRespVO.java
@@ -0,0 +1,33 @@
+package cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+
+import java.time.LocalDateTime;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍏徃鍝佺墝淇℃伅 Response VO")
+@Data
+public class SystemCompanyConfigRespVO {
+
+    @Schema(description = "涓婚敭", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    private Long id;
+
+    @Schema(description = "鍏徃鍚嶇О", example = "瀹佸涔濇硴鍖栧伐")
+    private String companyName;
+
+    @Schema(description = "logo 姘镐箙鍏紑閾炬帴锛坧reviewURL锛�", example = "http://domain/admin-api/system/storage-blob/preview/xxx?publicKey=xxx")
+    private String logoUrl;
+
+    @Schema(description = "logo 瀵瑰簲鐨� blob 璁板綍 ID", example = "86")
+    private Long logoBlobId;
+
+    @Schema(description = "鐧诲綍椤垫爣棰�", example = "鍒堕�犺繍钀ョ鐞嗗钩鍙�")
+    private String loginTitle;
+
+    @Schema(description = "鐧诲綍椤垫弿杩�", example = "宸ョ▼鍖栥�侀珮鎬ц兘銆佽法缁勪欢搴撶殑鍓嶇妯$増")
+    private String loginDescription;
+
+    @Schema(description = "鍒涘缓鏃堕棿", requiredMode = Schema.RequiredMode.REQUIRED)
+    private LocalDateTime createTime;
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigSaveReqVO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigSaveReqVO.java
new file mode 100644
index 0000000..76193e6
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/controller/admin/companyconfig/vo/SystemCompanyConfigSaveReqVO.java
@@ -0,0 +1,35 @@
+package cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+@Schema(description = "绠$悊鍚庡彴 - 鍏徃鍝佺墝淇℃伅淇濆瓨 Request VO")
+@Data
+public class SystemCompanyConfigSaveReqVO {
+
+    @Schema(description = "涓婚敭锛堝崟琛岄厤缃紝鍙笉浼狅級", example = "1")
+    private Long id;
+
+    @Schema(description = "鍏徃鍚嶇О", requiredMode = Schema.RequiredMode.REQUIRED, example = "瀹佸涔濇硴鍖栧伐")
+    @NotBlank(message = "鍏徃鍚嶇О涓嶈兘涓虹┖")
+    @Size(max = 128, message = "鍏徃鍚嶇О涓嶈兘瓒呰繃128涓瓧绗�")
+    private String companyName;
+
+    @Schema(description = "logo 姘镐箙鍏紑閾炬帴锛坧reviewURL锛�", example = "http://domain/admin-api/system/storage-blob/preview/xxx?publicKey=xxx")
+    @Size(max = 512, message = "logo 鍦板潃涓嶈兘瓒呰繃512涓瓧绗�")
+    private String logoUrl;
+
+    @Schema(description = "logo 瀵瑰簲鐨� blob 璁板綍 ID", example = "86")
+    private Long logoBlobId;
+
+    @Schema(description = "鐧诲綍椤垫爣棰�", example = "鍒堕�犺繍钀ョ鐞嗗钩鍙�")
+    @Size(max = 128, message = "鐧诲綍椤垫爣棰樹笉鑳借秴杩�128涓瓧绗�")
+    private String loginTitle;
+
+    @Schema(description = "鐧诲綍椤垫弿杩�", example = "宸ョ▼鍖栥�侀珮鎬ц兘銆佽法缁勪欢搴撶殑鍓嶇妯$増")
+    @Size(max = 128, message = "鐧诲綍椤垫弿杩颁笉鑳借秴杩�128涓瓧绗�")
+    private String loginDescription;
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/companyconfig/SystemCompanyConfigDO.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/companyconfig/SystemCompanyConfigDO.java
new file mode 100644
index 0000000..0f877f4
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/dataobject/companyconfig/SystemCompanyConfigDO.java
@@ -0,0 +1,47 @@
+package cn.iocoder.yudao.module.system.dal.dataobject.companyconfig;
+
+import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableName;
+import lombok.Data;
+import lombok.EqualsAndHashCode;
+
+/**
+ * 鍏徃鍝佺墝淇℃伅閰嶇疆 DO
+ *
+ * 鍗曡閰嶇疆锛氫繚瀛樺叕鍙稿悕绉�/logo/鐧诲綍椤垫爣棰橈紝鍓嶇鍚姩鏃惰鍙栧苟鑱斿姩鐧诲綍椤点�佽彍鍗曟爮銆佺綉椤垫爣棰樸��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("system_company_config")
+@KeySequence("system_company_config_seq") // 鐢ㄤ簬 Oracle銆丳ostgreSQL銆並ingbase銆丏B2銆丠2 鏁版嵁搴撶殑涓婚敭鑷銆傚鏋滄槸 MySQL 绛夋暟鎹簱锛屽彲涓嶅啓銆�
+@Data
+@EqualsAndHashCode(callSuper = true)
+public class SystemCompanyConfigDO extends BaseDO {
+
+    /**
+     * 涓婚敭
+     */
+    private Long id;
+    /**
+     * 鍏徃鍚嶇О
+     */
+    private String companyName;
+    /**
+     * logo 姘镐箙鍏紑閾炬帴锛坧ublic-upload 杩斿洖鐨� previewURL锛�
+     */
+    private String logoUrl;
+    /**
+     * logo 瀵瑰簲鐨� blob 璁板綍 ID
+     */
+    private Long logoBlobId;
+    /**
+     * 鐧诲綍椤垫爣棰�
+     */
+    private String loginTitle;
+    /**
+     * 鐧诲綍椤垫弿杩�
+     */
+    private String loginDescription;
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/companyconfig/SystemCompanyConfigMapper.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/companyconfig/SystemCompanyConfigMapper.java
new file mode 100644
index 0000000..c098410
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/dal/mysql/companyconfig/SystemCompanyConfigMapper.java
@@ -0,0 +1,15 @@
+package cn.iocoder.yudao.module.system.dal.mysql.companyconfig;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.module.system.dal.dataobject.companyconfig.SystemCompanyConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+/**
+ * 鍏徃鍝佺墝淇℃伅閰嶇疆 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface SystemCompanyConfigMapper extends BaseMapperX<SystemCompanyConfigDO> {
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigService.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigService.java
new file mode 100644
index 0000000..e2483da
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigService.java
@@ -0,0 +1,28 @@
+package cn.iocoder.yudao.module.system.service.companyconfig;
+
+import cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo.SystemCompanyConfigSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.companyconfig.SystemCompanyConfigDO;
+
+/**
+ * 鍏徃鍝佺墝淇℃伅閰嶇疆 Service 鎺ュ彛
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface SystemCompanyConfigService {
+
+    /**
+     * 鑾峰緱鍏徃鍝佺墝淇℃伅锛堝崟琛岄厤缃級
+     *
+     * @return 鍏徃鍝佺墝淇℃伅锛屾湭閰嶇疆鏃惰繑鍥� null
+     */
+    SystemCompanyConfigDO getCompanyConfig();
+
+    /**
+     * 淇濆瓨鍏徃鍝佺墝淇℃伅锛堝崟琛� upsert锛�
+     *
+     * @param reqVO 淇濆瓨璇锋眰
+     * @return 涓婚敭
+     */
+    Long saveCompanyConfig(SystemCompanyConfigSaveReqVO reqVO);
+
+}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigServiceImpl.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigServiceImpl.java
new file mode 100644
index 0000000..c862d36
--- /dev/null
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/service/companyconfig/SystemCompanyConfigServiceImpl.java
@@ -0,0 +1,50 @@
+package cn.iocoder.yudao.module.system.service.companyconfig;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.system.controller.admin.companyconfig.vo.SystemCompanyConfigSaveReqVO;
+import cn.iocoder.yudao.module.system.dal.dataobject.companyconfig.SystemCompanyConfigDO;
+import cn.iocoder.yudao.module.system.dal.mysql.companyconfig.SystemCompanyConfigMapper;
+import jakarta.annotation.Resource;
+import org.springframework.stereotype.Service;
+
+import java.util.List;
+
+/**
+ * 鍏徃鍝佺墝淇℃伅閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+public class SystemCompanyConfigServiceImpl implements SystemCompanyConfigService {
+
+    @Resource
+    private SystemCompanyConfigMapper companyConfigMapper;
+
+    @Override
+    public SystemCompanyConfigDO getCompanyConfig() {
+        // 鍗曡閰嶇疆锛氭寜涓婚敭鍗囧簭鍙栫涓�鏉�
+        List<SystemCompanyConfigDO> list = companyConfigMapper.selectList(new LambdaQueryWrapperX<SystemCompanyConfigDO>()
+                .orderByAsc(SystemCompanyConfigDO::getId)
+                .last("LIMIT 1"));
+        return CollUtil.getFirst(list);
+    }
+
+    @Override
+    public Long saveCompanyConfig(SystemCompanyConfigSaveReqVO reqVO) {
+        SystemCompanyConfigDO config = getCompanyConfig();
+        if (config == null) {
+            // 棣栨淇濆瓨锛氭彃鍏�
+            SystemCompanyConfigDO insertObj = BeanUtils.toBean(reqVO, SystemCompanyConfigDO.class);
+            companyConfigMapper.insert(insertObj);
+            return insertObj.getId();
+        }
+        // 宸插瓨鍦細鏇存柊鍗曡
+        SystemCompanyConfigDO updateObj = BeanUtils.toBean(reqVO, SystemCompanyConfigDO.class);
+        updateObj.setId(config.getId());
+        companyConfigMapper.updateById(updateObj);
+        return config.getId();
+    }
+
+}
diff --git a/yudao-server/src/main/resources/application-wtxc.yaml b/yudao-server/src/main/resources/application-wtxc.yaml
new file mode 100644
index 0000000..674f67a
--- /dev/null
+++ b/yudao-server/src/main/resources/application-wtxc.yaml
@@ -0,0 +1,222 @@
+server:
+  port: 48080
+
+--- #################### 鏁版嵁搴撶浉鍏抽厤缃� ####################
+spring:
+  autoconfigure:
+    # noinspection SpringBootApplicationYaml
+    exclude:
+      - org.springframework.boot.autoconfigure.quartz.QuartzAutoConfiguration # 榛樿 local 鐜锛屼笉寮�鍚� Quartz 鐨勮嚜鍔ㄩ厤缃�
+      - org.springframework.ai.vectorstore.qdrant.autoconfigure.QdrantVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Qdrant锛屾墜鍔ㄥ垱寤�
+      - org.springframework.ai.vectorstore.redis.autoconfigure.RedisVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Redis锛屾墜鍔ㄥ垱寤�
+      - org.springframework.ai.vectorstore.milvus.autoconfigure.MilvusVectorStoreAutoConfiguration # 绂佺敤 AI 妯″潡鐨� Milvus锛屾墜鍔ㄥ垱寤�
+      - org.dromara.trans.config.TransServiceConfig # TODO 鑺嬭壙锛歟asy-trans 鍏煎 Spring Boot 4 鍚庣Щ闄わ紙TransServiceConfig 寮曠敤浜嗗凡绉婚櫎鐨� RestTemplateBuilder锛�
+      # 绂佺敤鏈厤缃� API Key 鐨勬ā鍨嬭嚜鍔ㄩ厤缃紙鍙繚鐣欓�氫箟鍗冮棶 Embedding 鐨勬墜鍔ㄩ厤缃級
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiChatAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiEmbeddingAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiImageAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioSpeechAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiAudioTranscriptionAutoConfiguration
+      - org.springframework.ai.model.openai.autoconfigure.OpenAiModerationAutoConfiguration
+      - org.springframework.ai.model.deepseek.autoconfigure.DeepSeekChatAutoConfiguration
+      - org.springframework.ai.model.anthropic.autoconfigure.AnthropicChatAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.chat.GoogleGenAiChatAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiEmbeddingConnectionAutoConfiguration
+      - org.springframework.ai.model.google.genai.autoconfigure.embedding.GoogleGenAiTextEmbeddingAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaApiAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaChatAutoConfiguration
+      - org.springframework.ai.model.ollama.autoconfigure.OllamaEmbeddingAutoConfiguration
+      - org.springframework.ai.model.stabilityai.autoconfigure.StabilityAiImageAutoConfiguration
+  # 鏁版嵁婧愰厤缃」
+  datasource:
+    druid: # Druid 銆愮洃鎺с�戠浉鍏崇殑鍏ㄥ眬閰嶇疆
+      web-stat-filter:
+        enabled: true
+      stat-view-servlet:
+        enabled: true
+        allow: # 璁剧疆鐧藉悕鍗曪紝涓嶅~鍒欏厑璁告墍鏈夎闂�
+        url-pattern: /druid/*
+        login-username: # 鎺у埗鍙扮鐞嗙敤鎴峰悕鍜屽瘑鐮�
+        login-password:
+      filter:
+        stat:
+          enabled: true
+          log-slow-sql: true # 鎱� SQL 璁板綍
+          slow-sql-millis: 100
+          merge-sql: true
+        wall:
+          config:
+            multi-statement-allow: true
+    dynamic: # 澶氭暟鎹簮閰嶇疆
+      druid: # Druid 銆愯繛鎺ユ睜銆戠浉鍏崇殑鍏ㄥ眬閰嶇疆
+        initial-size: 1 # 鍒濆杩炴帴鏁�
+        min-idle: 1 # 鏈�灏忚繛鎺ユ睜鏁伴噺
+        max-active: 20 # 鏈�澶ц繛鎺ユ睜鏁伴噺
+        max-wait: 60000 # 閰嶇疆鑾峰彇杩炴帴绛夊緟瓒呮椂鐨勬椂闂达紝鍗曚綅锛氭绉掞紙1 鍒嗛挓锛�
+        time-between-eviction-runs-millis: 60000 # 閰嶇疆闂撮殧澶氫箙鎵嶈繘琛屼竴娆℃娴嬶紝妫�娴嬮渶瑕佸叧闂殑绌洪棽杩炴帴锛屽崟浣嶏細姣锛�1 鍒嗛挓锛�
+        min-evictable-idle-time-millis: 600000 # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�灏忕敓瀛樼殑鏃堕棿锛屽崟浣嶏細姣锛�10 鍒嗛挓锛�
+        max-evictable-idle-time-millis: 1800000 # 閰嶇疆涓�涓繛鎺ュ湪姹犱腑鏈�澶х敓瀛樼殑鏃堕棿锛屽崟浣嶏細姣锛�30 鍒嗛挓锛�
+        validation-query: SELECT 1 FROM DUAL # 閰嶇疆妫�娴嬭繛鎺ユ槸鍚︽湁鏁�
+        test-while-idle: true
+        test-on-borrow: false
+        test-on-return: false
+        pool-prepared-statements: true # 鏄惁寮�鍚� PreparedStatement 缂撳瓨
+        max-pool-prepared-statement-per-connection-size: 20 # 姣忎釜杩炴帴缂撳瓨鐨� PreparedStatement 鏁伴噺
+      primary: master
+      datasource:
+        master:
+          url: jdbc:mysql://172.17.0.1:9002/mom-wtxc?useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true&nullCatalogMeansCurrent=true&rewriteBatchedStatements=true # MySQL Connector/J 8.X 杩炴帴鐨勭ず渚�
+          #          url: jdbc:mysql://127.0.0.1:3306/ruoyi-vue-pro?useSSL=true&allowPublicKeyRetrieval=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai&rewriteBatchedStatements=true # MySQL Connector/J 5.X 杩炴帴鐨勭ず渚�
+          #          url: jdbc:postgresql://127.0.0.1:5432/ruoyi-vue-pro # PostgreSQL 杩炴帴鐨勭ず渚�
+          #          url: jdbc:oracle:thin:@127.0.0.1:1521:xe # Oracle 杩炴帴鐨勭ず渚�
+          #          url: jdbc:sqlserver://127.0.0.1:1433;DatabaseName=ruoyi-vue-pro;SelectMethod=cursor;encrypt=false;rewriteBatchedStatements=true;useUnicode=true;characterEncoding=utf-8 # SQLServer 杩炴帴鐨勭ず渚�
+          #          url: jdbc:dm://127.0.0.1:5236?schema=RUOYI_VUE_PRO # DM 杩炴帴鐨勭ず渚�
+          #          url: jdbc:kingbase8://127.0.0.1:54321/test  # 浜哄ぇ閲戜粨 KingbaseES 杩炴帴鐨勭ず渚�
+          #          url: jdbc:postgresql://127.0.0.1:5432/postgres # OpenGauss 杩炴帴鐨勭ず渚�
+          #          url: jdbc:postgresql://127.0.0.1:5866/ruoyi-vue-pro # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+          username: root
+          password: wtxc@123456..
+          #          username: sa # SQL Server 杩炴帴鐨勭ず渚�
+          #          password: Yudao@2024 # SQL Server 杩炴帴鐨勭ず渚�
+          #          username: SYSDBA # DM 杩炴帴鐨勭ず渚�
+          #          password: SYSDBA001 # DM 杩炴帴鐨勭ず渚�
+          #          username: root # OpenGauss 杩炴帴鐨勭ず渚�
+          #          password: Yudao@2024 # OpenGauss 杩炴帴鐨勭ず渚�
+          #          username: root # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+          #          password: 123456 # 鐎氶珮 HighGo 杩炴帴鐨勭ず渚�
+#        tdengine: # IoT 鏁版嵁搴擄紙闇�瑕� IoT 鐗╄仈缃戝啀寮�鍚櫌锛侊級
+#          lazy: true # 寮�鍚噿鍔犺浇锛屼繚璇佸惎鍔ㄩ�熷害
+#          url: jdbc:TAOS-WS://127.0.0.1:6041/ruoyi_vue_pro?varcharAsString=true&enableAutoReconnect=true # 鍙傝锛歨ttps://t.zsxq.com/qaX9c锛屽紑鍚� TDengine WebSocket 鑷姩閲嶈繛
+#          driver-class-name: com.taosdata.jdbc.ws.WebSocketDriver
+#          username: root
+#          password: taosdata
+#          druid:
+#            validation-query: SELECT SERVER_STATUS() # TDengine 鏁版嵁婧愮殑鏈夋晥鎬ф鏌� SQL
+
+  # Redis 閰嶇疆銆俁edisson 榛樿鐨勯厤缃冻澶熶娇鐢紝涓�鑸笉闇�瑕佽繘琛岃皟浼�
+  data:
+    redis:
+      host: 172.17.0.1 # 鍦板潃
+      port: 6379 # 绔彛
+      database: 1 # 鏁版嵁搴撶储寮�
+#      password: dev # 瀵嗙爜锛屽缓璁敓浜х幆澧冨紑鍚�
+
+--- #################### 瀹氭椂浠诲姟鐩稿叧閰嶇疆 ####################
+
+# Quartz 閰嶇疆椤癸紝瀵瑰簲 QuartzProperties 閰嶇疆绫�
+spring:
+  quartz:
+    auto-startup: true # 鏈湴寮�鍙戠幆澧冿紝灏介噺涓嶈寮�鍚� Job
+    scheduler-name: schedulerName # Scheduler 鍚嶅瓧銆傞粯璁や负 schedulerName
+    job-store-type: jdbc # Job 瀛樺偍鍣ㄧ被鍨嬨�傞粯璁や负 memory 琛ㄧず鍐呭瓨锛屽彲閫� jdbc 浣跨敤鏁版嵁搴撱��
+    wait-for-jobs-to-complete-on-shutdown: true # 搴旂敤鍏抽棴鏃讹紝鏄惁绛夊緟瀹氭椂浠诲姟鎵ц瀹屾垚銆傞粯璁や负 false 锛屽缓璁缃负 true
+    properties: # 娣诲姞 Quartz Scheduler 闄勫姞灞炴�э紝鏇村鍙互鐪� http://www.quartz-scheduler.org/documentation/2.4.0-SNAPSHOT/configuration.html 鏂囨。
+      org:
+        quartz:
+          # Scheduler 鐩稿叧閰嶇疆
+          scheduler:
+            instanceName: schedulerName
+            instanceId: AUTO # 鑷姩鐢熸垚 instance ID
+          # JobStore 鐩稿叧閰嶇疆
+          jobStore:
+            # JobStore 瀹炵幇绫汇�傚彲瑙佸崥瀹細https://blog.csdn.net/weixin_42458219/article/details/122247162
+            class: org.springframework.scheduling.quartz.LocalDataSourceJobStore
+            isClustered: true # 鏄泦缇ゆā寮�
+            clusterCheckinInterval: 15000 # 闆嗙兢妫�鏌ラ鐜囷紝鍗曚綅锛氭绉掋�傞粯璁や负 15000锛屽嵆 15 绉�
+            misfireThreshold: 60000 # misfire 闃�鍊硷紝鍗曚綅锛氭绉掋��
+          # 绾跨▼姹犵浉鍏抽厤缃�
+          threadPool:
+            threadCount: 25 # 绾跨▼姹犲ぇ灏忋�傞粯璁や负 10 銆�
+            threadPriority: 5 # 绾跨▼浼樺厛绾�
+            class: org.quartz.simpl.SimpleThreadPool # 绾跨▼姹犵被鍨�
+    jdbc: # 浣跨敤 JDBC 鐨� JobStore 鐨勬椂鍊欙紝JDBC 鐨勯厤缃�
+      initialize-schema: NEVER # 鏄惁鑷姩浣跨敤 SQL 鍒濆鍖� Quartz 琛ㄧ粨鏋勩�傝繖閲岃缃垚 never 锛屾垜浠墜鍔ㄥ垱寤鸿〃缁撴瀯銆�
+
+--- #################### 鏈嶅姟淇濋殰鐩稿叧閰嶇疆 ####################
+
+# Lock4j 閰嶇疆椤�
+lock4j:
+  acquire-timeout: 3000 # 鑾峰彇鍒嗗竷寮忛攣瓒呮椂鏃堕棿锛岄粯璁や负 3000 姣
+  expire: 30000 # 鍒嗗竷寮忛攣鐨勮秴鏃舵椂闂达紝榛樿涓� 30000 姣
+
+--- #################### 鐩戞帶鐩稿叧閰嶇疆 ####################
+
+# Actuator 鐩戞帶绔偣鐨勯厤缃」
+management:
+  endpoints:
+    web:
+      base-path: /actuator # Actuator 鎻愪緵鐨� API 鎺ュ彛鐨勬牴鐩綍銆傞粯璁や负 /actuator
+      exposure:
+        include: '*' # 闇�瑕佸紑鏀剧殑绔偣銆傞粯璁ゅ�煎彧鎵撳紑 health 鍜� info 涓や釜绔偣銆傞�氳繃璁剧疆 * 锛屽彲浠ュ紑鏀炬墍鏈夌鐐广��
+
+# Spring Boot Admin 閰嶇疆椤�
+spring:
+  boot:
+    admin:
+      # Spring Boot Admin Client 瀹㈡埛绔殑鐩稿叧閰嶇疆
+      client:
+        url: http://127.0.0.1:${server.port}/${spring.boot.admin.context-path} # 璁剧疆 Spring Boot Admin Server 鍦板潃
+        instance:
+          service-host-type: IP # 娉ㄥ唽瀹炰緥鏃讹紝浼樺厛浣跨敤 IP [IP, HOST_NAME, CANONICAL_HOST_NAME]
+        username: admin
+        password: admin
+      # Spring Boot Admin Server 鏈嶅姟绔殑鐩稿叧閰嶇疆
+      context-path: /admin # 閰嶇疆 Spring
+      # 鍏佽宓屽叆 iframe 鐨勫煙鍚嶏紙鏀寔閫氶厤绗︼級锛屽疄闄呴儴缃叉椂锛屽彲浠ユ敼涓� "'self' [浣犵殑鍏綉鍩熷悕]"
+      frame-ancestors: "'self' localhost localhost:48080 127.0.0.1 127.0.0.1:48080"
+
+# 鏃ュ織鏂囦欢閰嶇疆
+logging:
+  file:
+    name: logs/${spring.application.name}.log # 鏃ュ織鏂囦欢鍚嶏紝鐩稿浜庨」鐩牴鐩綍
+  level:
+    # 閰嶇疆鑷繁鍐欑殑 MyBatis Mapper 鎵撳嵃鏃ュ織
+    cn.iocoder.yudao.module.bpm.dal.mysql: debug
+    cn.iocoder.yudao.module.infra.dal.mysql: debug
+    cn.iocoder.yudao.module.infra.dal.mysql.logger.ApiErrorLogMapper: INFO # 閰嶇疆 ApiErrorLogMapper 鐨勬棩蹇楃骇鍒负 info锛岄伩鍏嶅拰 GlobalExceptionHandler 閲嶅鎵撳嵃
+    cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 閰嶇疆 JobLogMapper 鐨勬棩蹇楃骇鍒负 info
+    cn.iocoder.yudao.module.infra.dal.mysql.file.FileConfigMapper: INFO # 閰嶇疆 FileConfigMapper 鐨勬棩蹇楃骇鍒负 info
+    cn.iocoder.yudao.module.system.dal.mysql: debug
+    cn.iocoder.yudao.module.system.dal.mysql.sms.SmsChannelMapper: INFO # 閰嶇疆 SmsChannelMapper 鐨勬棩蹇楃骇鍒负 info
+    cn.iocoder.yudao.module.member.dal.mysql: debug
+    cn.iocoder.yudao.module.crm.dal.mysql: debug
+    cn.iocoder.yudao.module.erp.dal.mysql: debug
+    cn.iocoder.yudao.module.mes.dal.mysql: debug
+    cn.iocoder.yudao.module.ai.dal.mysql: debug
+    cn.iocoder.yudao.module.im.dal.mysql: debug
+    org.springframework.context.support.PostProcessorRegistrationDelegate: ERROR # TODO 鑺嬭壙锛氬厛绂佺敤锛孲pring Boot 3.X 瀛樺湪閮ㄥ垎閿欒鐨� WARN 鎻愮ず
+
+debug: false
+
+yudao:
+  ai:
+    vector-store:
+      type: milvus # 鍚戦噺搴撶被鍨嬶細milvus
+      milvus:
+        initialize-schema: true
+        database-name: test
+        collection-name: knowledge_segment_wtxc
+        host: 36.213.90.123
+        port: 19530
+
+--- #################### 瓒呯骇绠$悊鍛樼浉鍏抽厤缃� ####################
+
+# 瓒呯骇绠$悊鍛橀厤缃」锛岃缃綋鍓嶉」鐩墍鏈夎嚜瀹氫箟鐨勯厤缃�
+yudao:
+  captcha:
+    enable: false # 鏈湴鐜锛屾殏鏃跺叧闂浘鐗囬獙璇佺爜锛屾柟渚跨櫥褰曠瓑鎺ュ彛鐨勬祴璇曪紱
+  security:
+    mock-enable: true
+  access-log: # 璁块棶鏃ュ織鐨勯厤缃」
+    enable: false
+  demo: false # 鍏抽棴婕旂ず妯″紡
+  storage: # 鏂囦欢涓婁紶閰嶇疆
+    path: /data/javaWork/mom/file # 鏂囦欢瀛樺偍鏍圭洰褰�
+    url-prefix: /admin-api/system/storage-blob # URL璺緞鍓嶇紑
+    domain: http://42.63.71.140:9001 # 鍩熷悕鍓嶇紑锛堟寜瀹為檯鍩熷悕閰嶇疆锛夛紝鍓嶇閰嶇疆濂藉儚涓嶅お瀵圭幇鍦ㄦ槸鐩磋繛鍚庣锛屾墍鏈夎鍚庣绔彛
+    expired: 120 # token杩囨湡鏃堕棿锛堝垎閽燂級
+    use-limit: 10 # token浣跨敤娆℃暟涓婇檺
+    jwt-secret: abcdefghijklmnopqrstuvwxyz0123456789ABCDEFG # JWT瀵嗛挜
+    compress: true # 鏄惁鍘嬬缉鍥剧墖
+    need-compress-size: 10MB # 瑙﹀彂鍘嬬缉鐨勬枃浠跺ぇ灏忛槇鍊�
+    compress-quality: 0.5 # 鍘嬬缉璐ㄩ噺(0.0-1.0)

--
Gitblit v1.9.3