From fdbc5af6474abb39120b771af87005aeb6186ae5 Mon Sep 17 00:00:00 2001
From: tuqin <tuqin@tuqin.cn>
Date: 星期一, 14 九月 2026 15:28:28 +0800
Subject: [PATCH] feat: 售后工单按条绑定部门与原因类型;新增加班工资管理;员工附件留档

---
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeCategoryEnum.java                                        |   40 ++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java                           |    9 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigSaveReqVO.java                |   39 ++
 sql/mysql/hrm_overtime_wage_config_init.sql                                                                                              |   89 ++++++
 yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java                                |    1 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java          |   16 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/employee/HrmEmployeeServiceImpl.java                                  |   12 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java                                    |    7 
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java                |   18 +
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                             |    2 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigService.java                              |   24 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigRespVO.java                   |   37 ++
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java    |   15 +
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java |   11 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java                        |    6 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmOvertimeWageConfigDO.java                            |   64 ++++
 yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java                  |   23 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmOvertimeWageConfigController.java                  |  112 ++++++++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigServiceImpl.java                          |   83 +++++
 sql/mysql/aftersales_ticket_item_enhance.sql                                                                                             |   44 +++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmOvertimeWageConfigMapper.java                             |   39 ++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java                           |  135 +++++++-
 22 files changed, 793 insertions(+), 33 deletions(-)

diff --git a/sql/mysql/aftersales_ticket_item_enhance.sql b/sql/mysql/aftersales_ticket_item_enhance.sql
new file mode 100644
index 0000000..818614d
--- /dev/null
+++ b/sql/mysql/aftersales_ticket_item_enhance.sql
@@ -0,0 +1,44 @@
+-- =============================================
+-- 鍞悗宸ュ崟鏄庣粏澧炲己 SQL 鍒濆鍖栬剼鏈紙鍙噸澶嶆墽琛岋級
+-- 闇�姹傦細鍞悗閫�璐�/宸ュ崟姣忔潯鏄庣粏鐙珛鍞悗鍗曞彿锛屾瘡鏉¢�夐儴闂ㄣ�佸師鍥犵被鍨嬪苟濉叿浣撳師鍥�
+-- 鍖呭惈锛歛fter_sale_ticket_item 鍔犲瓧娈� + aftersale_reason_type 瀛楀吀
+-- =============================================
+
+-- =============================================
+-- 1. 宸ュ崟鏄庣粏琛ㄦ柊澧炲瓧娈碉紙瀛樺湪鎬у垽鏂紝淇濊瘉鍙噸澶嶆墽琛岋級
+-- =============================================
+SET @col_exists := (SELECT COUNT(*) FROM information_schema.COLUMNS
+                    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'after_sale_ticket_item' AND COLUMN_NAME = 'item_no');
+SET @ddl := IF(@col_exists = 0,
+    'ALTER TABLE `after_sale_ticket_item`
+        ADD COLUMN `item_no` varchar(64) NULL COMMENT ''鏄庣粏鍞悗鍗曞彿锛堝伐鍗曞彿-搴忓彿锛�'' AFTER `ticket_id`,
+        ADD COLUMN `dept_id` bigint NULL COMMENT ''閮ㄩ棬ID锛屽叧鑱� system_dept.id'' AFTER `need_return`,
+        ADD COLUMN `reason_type` tinyint NULL COMMENT ''鍘熷洜绫诲瀷锛屽瓧鍏� aftersale_reason_type'' AFTER `dept_id`,
+        ADD COLUMN `reason` varchar(500) NULL COMMENT ''鍏蜂綋鍘熷洜'' AFTER `reason_type`',
+    'SELECT 1');
+PREPARE stmt FROM @ddl;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================
+-- 2. 鏁版嵁瀛楀吀锛氬敭鍚庡師鍥犵被鍨� aftersale_reason_type
+--    锛堝彇鍊煎彲鐢变笟鍔″湪銆岀郴缁熺鐞� - 瀛楀吀绠$悊銆嶄腑鑷缁存姢锛�
+-- =============================================
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1065307, '鍞悗鍘熷洜绫诲瀷', 'aftersale_reason_type', 0, '鍞悗宸ュ崟鏄庣粏鍘熷洜鍒嗙被', '', NOW(), '', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `type` = VALUES(`type`), `status` = VALUES(`status`), `deleted` = b'0';
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1065352, 1, '澶栬缂洪櫡', '1', 'aftersale_reason_type', 0, 'primary', NULL, '', NOW(), '', NOW(), b'0'),
+(1065353, 2, '鍔熻兘鏁呴殰', '2', 'aftersale_reason_type', 0, 'warning', NULL, '', NOW(), '', NOW(), b'0'),
+(1065354, 3, '灏哄鍋忓樊', '3', 'aftersale_reason_type', 0, 'info', NULL, '', NOW(), '', NOW(), b'0'),
+(1065355, 4, '鍖呰鐮存崯', '4', 'aftersale_reason_type', 0, 'warning', NULL, '', NOW(), '', NOW(), b'0'),
+(1065356, 5, '鐗╂祦鎹熷潖', '5', 'aftersale_reason_type', 0, 'danger', NULL, '', NOW(), '', NOW(), b'0'),
+(1065357, 6, '缂轰欢灏戝彂', '6', 'aftersale_reason_type', 0, 'danger', NULL, '', NOW(), '', NOW(), b'0'),
+(1065358, 7, '瑙勬牸鍨嬪彿涓嶇', '7', 'aftersale_reason_type', 0, 'info', NULL, '', NOW(), '', NOW(), b'0'),
+(1065359, 8, '浣跨敤涓嶅綋', '8', 'aftersale_reason_type', 0, 'default', NULL, '', NOW(), '', NOW(), b'0'),
+(1065360, 9, '鍏朵粬', '9', 'aftersale_reason_type', 0, 'default', NULL, '', NOW(), '', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `sort` = VALUES(`sort`), `label` = VALUES(`label`), `value` = VALUES(`value`),
+    `dict_type` = VALUES(`dict_type`), `status` = VALUES(`status`), `color_type` = VALUES(`color_type`), `deleted` = b'0';
+
+SELECT '鍞悗宸ュ崟鏄庣粏澧炲己鍒濆鍖栧畬鎴�' AS result;
diff --git a/sql/mysql/hrm_overtime_wage_config_init.sql b/sql/mysql/hrm_overtime_wage_config_init.sql
new file mode 100644
index 0000000..b3589a9
--- /dev/null
+++ b/sql/mysql/hrm_overtime_wage_config_init.sql
@@ -0,0 +1,89 @@
+-- =============================================
+-- HRM 鍔犵彮宸ヨ祫绠$悊 SQL 鍒濆鍖栬剼鏈紙鍙噸澶嶆墽琛岋級
+-- 鍖呭惈锛氬矖浣嶅姞鐝伐璧勯厤缃〃 + 鍛樺伐銆屽憳宸ョ被鍒�嶅瓧娈� + 鏁版嵁瀛楀吀 + 鑿滃崟/鏉冮檺
+-- 瀵瑰簲闇�姹傦細浜哄姏璧勬簮 > 钖叕绠$悊 > 鍔犵彮宸ヨ祫绠$悊
+--   - 涓嶅悓宀椾綅鍔犵彮宸ヨ祫涓嶅悓锛堟櫘閫氬憳宸ユ寜宀椾綅鏃惰柂锛涙妧宸� 1:1锛涚鐞嗕笉璁★級
+--   - 鎸夊伐绉嶈缃渶澶у姞鐝椂闀夸笂闄�
+-- =============================================
+
+-- =============================================
+-- 1. 宀椾綅鍔犵彮宸ヨ祫閰嶇疆琛�
+-- =============================================
+CREATE TABLE IF NOT EXISTS `hrm_overtime_wage_config` (
+    `id` bigint NOT NULL AUTO_INCREMENT COMMENT '閰嶇疆ID',
+    `post_id` bigint NOT NULL COMMENT '宀椾綅ID锛屽叧鑱� system_post.id',
+    `hourly_rate` decimal(10,2) NOT NULL DEFAULT 20.00 COMMENT '鍔犵彮鏃惰柂锛堝厓/灏忔椂锛夛紝榛樿 20',
+    `max_overtime_hours` decimal(6,1) DEFAULT NULL COMMENT '姣忔湀鏈�澶у姞鐝椂闀夸笂闄愶紙灏忔椂锛夛紝涓虹┖琛ㄧず涓嶉檺',
+    `status` tinyint NOT NULL DEFAULT 0 COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
+    `remark` varchar(500) 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_post_id` (`post_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='HRM宀椾綅鍔犵彮宸ヨ祫閰嶇疆琛�';
+
+-- =============================================
+-- 2. 鍛樺伐琛ㄦ柊澧炪�屽憳宸ョ被鍒�嶅瓧娈碉紙1-鏅�氬憳宸ワ紝2-鎶�宸ワ紝3-绠$悊锛�
+-- 璇存槑锛欰DD COLUMN 涓嶆敮鎸� IF NOT EXISTS锛岃繖閲岀敤 information_schema 鍋氬瓨鍦ㄦ�у垽鏂紝淇濊瘉鍙噸澶嶆墽琛�
+-- =============================================
+SET @col_exists := (SELECT COUNT(*) FROM information_schema.COLUMNS
+                    WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'hrm_employee' AND COLUMN_NAME = 'employee_category');
+SET @ddl := IF(@col_exists = 0,
+    'ALTER TABLE `hrm_employee` ADD COLUMN `employee_category` tinyint NOT NULL DEFAULT 1 COMMENT ''鍛樺伐绫诲埆锛�1-鏅�氬憳宸ワ紝2-鎶�宸ワ紝3-绠$悊'' AFTER `employee_status`',
+    'SELECT 1');
+PREPARE stmt FROM @ddl;
+EXECUTE stmt;
+DEALLOCATE PREPARE stmt;
+
+-- =============================================
+-- 3. 鏁版嵁瀛楀吀锛氬憳宸ョ被鍒� hrm_employee_category
+-- =============================================
+INSERT INTO `system_dict_type` (`id`, `name`, `type`, `status`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1061300, 'HRM 鍛樺伐绫诲埆', 'hrm_employee_category', 0, '鍔犵彮宸ヨ祫宸紓鏍哥畻鐢�', '', NOW(), '', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `name` = VALUES(`name`), `type` = VALUES(`type`), `status` = VALUES(`status`);
+
+INSERT INTO `system_dict_data` (`id`, `sort`, `label`, `value`, `dict_type`, `status`, `color_type`, `remark`, `creator`, `create_time`, `updater`, `update_time`, `deleted`) VALUES
+(1061301, 1, '鏅�氬憳宸�', '1', 'hrm_employee_category', 0, 'primary', '鎸夊矖浣嶉厤缃殑鍔犵彮鏃惰柂鏍哥畻', '', NOW(), '', NOW(), b'0'),
+(1061302, 2, '鎶�宸�', '2', 'hrm_employee_category', 0, 'warning', '鍔犵彮鎸� 1:1锛屼笌骞虫椂鏃惰柂涓�鑷�', '', NOW(), '', NOW(), b'0'),
+(1061303, 3, '绠$悊', '3', 'hrm_employee_category', 0, 'default', '涓嶈鍔犵彮宸ヨ祫', '', NOW(), '', NOW(), b'0')
+ON DUPLICATE KEY UPDATE `label` = VALUES(`label`), `value` = VALUES(`value`), `dict_type` = VALUES(`dict_type`), `status` = VALUES(`status`), `color_type` = VALUES(`color_type`);
+
+-- =============================================
+-- 4. 鑿滃崟涓庢潈闄愶紙鎸傝浇鍒� 6020 钖叕绠$悊 涓嬶級
+-- =============================================
+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
+(6026, '鍔犵彮宸ヨ祫绠$悊', '', 2, 4, 6020, 'overtime-wage', 'ep:clock',
+ 'hrm/salary/overtime-wage/index', 'HrmOvertimeWage', 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60261, '鍔犵彮宸ヨ祫鏌ヨ', 'hrm:overtime-wage:query', 3, 1, 6026, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60262, '鍔犵彮宸ヨ祫鏂板', 'hrm:overtime-wage:create', 3, 2, 6026, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60263, '鍔犵彮宸ヨ祫淇敼', 'hrm:overtime-wage:update', 3, 3, 6026, '', '', '', NULL,
+ 0, b'1', b'1', b'1', '1', NOW(), '1', NOW(), b'0'),
+(60264, '鍔犵彮宸ヨ祫鍒犻櫎', 'hrm:overtime-wage:delete', 3, 4, 6026, '', '', '', 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';
+
+-- 鎺堜簣瓒呯骇绠$悊鍛橈紙role_id = 1锛�
+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 6026 AS menu_id UNION ALL SELECT 60261 UNION ALL SELECT 60262
+    UNION ALL SELECT 60263 UNION ALL SELECT 60264
+) 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/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java
index 4f6cddd..32f160a 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/AfterSaleTicketController.java
@@ -13,6 +13,8 @@
 import cn.iocoder.yudao.module.aftersales.service.ticket.AfterSaleTicketService;
 import cn.iocoder.yudao.module.crm.api.customer.CrmCustomerApi;
 import cn.iocoder.yudao.module.crm.api.customer.dto.CrmCustomerRespDTO;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
@@ -43,6 +45,8 @@
     private CrmCustomerApi customerApi;
     @Resource
     private AdminUserApi adminUserApi;
+    @Resource
+    private DeptApi deptApi;
     @Resource
     private cn.iocoder.yudao.module.system.api.storage.StorageAttachmentApi storageAttachmentApi;
 
@@ -80,7 +84,17 @@
         // 鎵归噺鍔犺浇鍏宠仈鍚嶇О
         List<AfterSaleTicketRespVO> enrichedList = buildTicketDetailList(List.of(ticket));
         AfterSaleTicketRespVO respVO = enrichedList.get(0);
-        respVO.setItems(BeanUtils.toBean(items, AfterSaleTicketRespVO.Item.class));
+        List<AfterSaleTicketRespVO.Item> itemVOs = BeanUtils.toBean(items, AfterSaleTicketRespVO.Item.class);
+        // 鍥炴樉鏄庣粏閮ㄩ棬鍚嶇О
+        Set<Long> deptIds = convertSet(itemVOs, AfterSaleTicketRespVO.Item::getDeptId);
+        Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(deptIds);
+        itemVOs.forEach(itemVO -> {
+            DeptRespDTO dept = deptMap.get(itemVO.getDeptId());
+            if (dept != null) {
+                itemVO.setDeptName(dept.getName());
+            }
+        });
+        respVO.setItems(itemVOs);
         respVO.setAttachmentList(storageAttachmentApi.listAttachments("aftersales_after_sale_ticket", id));
         return success(respVO);
     }
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java
index f8031a1..8223a42 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketRespVO.java
@@ -148,6 +148,21 @@
         @Schema(description = "鏄惁闇�瑕侀��璐�", example = "true")
         private Boolean needReturn;
 
+        @Schema(description = "鏄庣粏鍞悗鍗曞彿", example = "AS20240101000001-01")
+        private String itemNo;
+
+        @Schema(description = "閮ㄩ棬ID", example = "1")
+        private Long deptId;
+
+        @Schema(description = "閮ㄩ棬鍚嶇О")
+        private String deptName;
+
+        @Schema(description = "鍘熷洜绫诲瀷", example = "1")
+        private Integer reasonType;
+
+        @Schema(description = "鍏蜂綋鍘熷洜", example = "浜у搧琛ㄩ潰鏈夊垝鐥�")
+        private String reason;
+
         @Schema(description = "澶囨敞")
         private String remark;
     }
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java
index 6e7c96f..a644776 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/controller/admin/ticket/vo/ticket/AfterSaleTicketSaveReqVO.java
@@ -94,6 +94,17 @@
         @Schema(description = "鏄惁闇�瑕侀��璐�", example = "true")
         private Boolean needReturn;
 
+        @Schema(description = "閮ㄩ棬ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "閮ㄩ棬涓嶈兘涓虹┖")
+        private Long deptId;
+
+        @Schema(description = "鍘熷洜绫诲瀷", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+        @NotNull(message = "鍘熷洜绫诲瀷涓嶈兘涓虹┖")
+        private Integer reasonType;
+
+        @Schema(description = "鍏蜂綋鍘熷洜", example = "浜у搧琛ㄩ潰鏈夊垝鐥�")
+        private String reason;
+
         @Schema(description = "澶囨敞")
         private String remark;
     }
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java
index 28af429..df3f1c7 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/dal/dataobject/ticket/AfterSaleTicketItemDO.java
@@ -31,6 +31,10 @@
      */
     private Long ticketId;
     /**
+     * 鏄庣粏鍞悗鍗曞彿锛堝伐鍗曞彿-搴忓彿锛�
+     */
+    private String itemNo;
+    /**
      * 閿�鍞鍗曡缂栧彿
      */
     private Long saleOrderItemId;
@@ -71,6 +75,20 @@
      */
     private Boolean needReturn;
     /**
+     * 閮ㄩ棬ID锛屽叧鑱� system_dept.id
+     */
+    private Long deptId;
+    /**
+     * 鍘熷洜绫诲瀷
+     *
+     * 瀛楀吀 aftersale_reason_type
+     */
+    private Integer reasonType;
+    /**
+     * 鍏蜂綋鍘熷洜
+     */
+    private String reason;
+    /**
      * 澶囨敞
      */
     private String remark;
diff --git a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
index c509cce..294181a 100644
--- a/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
+++ b/yudao-module-aftersales/src/main/java/cn/iocoder/yudao/module/aftersales/service/ticket/AfterSaleTicketServiceImpl.java
@@ -67,13 +67,14 @@
                 .setOwnerUserId(userId);
         ticketMapper.insert(ticket);
 
-        // 3. 鎻掑叆鏄庣粏琛�
+        // 3. 鎻掑叆鏄庣粏琛岋紙姣忔潯鐢熸垚鐙珛鍞悗鍗曞彿锛氬伐鍗曞彿-搴忓彿锛�
         if (CollUtil.isNotEmpty(createReqVO.getItems())) {
-            createReqVO.getItems().forEach(item -> {
-                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
+            for (int i = 0; i < createReqVO.getItems().size(); i++) {
+                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(createReqVO.getItems().get(i), AfterSaleTicketItemDO.class);
                 itemDO.setTicketId(ticket.getId());
+                itemDO.setItemNo(buildItemNo(no, i + 1));
                 ticketItemMapper.insert(itemDO);
-            });
+            }
         }
 
         // 4. 缁戝畾闄勪欢
@@ -102,14 +103,15 @@
         AfterSaleTicketDO updateObj = BeanUtils.toBean(updateReqVO, AfterSaleTicketDO.class);
         ticketMapper.updateById(updateObj);
 
-        // 3. 鏇存柊鏄庣粏琛岋紙鍏堝垹鍚庡锛�
+        // 3. 鏇存柊鏄庣粏琛岋紙鍏堝垹鍚庡锛屾瘡鏉$敓鎴愮嫭绔嬪敭鍚庡崟鍙凤細宸ュ崟鍙�-搴忓彿锛�
         ticketItemMapper.deleteByTicketId(ticket.getId());
         if (CollUtil.isNotEmpty(updateReqVO.getItems())) {
-            updateReqVO.getItems().forEach(item -> {
-                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(item, AfterSaleTicketItemDO.class);
+            for (int i = 0; i < updateReqVO.getItems().size(); i++) {
+                AfterSaleTicketItemDO itemDO = BeanUtils.toBean(updateReqVO.getItems().get(i), AfterSaleTicketItemDO.class);
                 itemDO.setTicketId(ticket.getId());
+                itemDO.setItemNo(buildItemNo(ticket.getNo(), i + 1));
                 ticketItemMapper.insert(itemDO);
-            });
+            }
         }
 
         // 4. 鏇存柊闄勪欢
@@ -204,6 +206,11 @@
 
     // ========== 鍐呴儴鏂规硶 ==========
 
+    /** 鐢熸垚鏄庣粏鍞悗鍗曞彿锛氬伐鍗曞彿-涓や綅搴忓彿 */
+    private String buildItemNo(String ticketNo, int index) {
+        return ticketNo + "-" + String.format("%02d", index);
+    }
+
     private AfterSaleTicketDO validateTicketExists(Long id) {
         AfterSaleTicketDO ticket = ticketMapper.selectById(id);
         if (ticket == 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 2133c71..275c031 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
@@ -123,6 +123,8 @@
     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湪琚憳宸ヤ娇鐢紝鏃犳硶鍒犻櫎");
+    ErrorCode OVERTIME_WAGE_CONFIG_NOT_EXISTS = new ErrorCode(1_030_005_027, "宀椾綅鍔犵彮宸ヨ祫閰嶇疆涓嶅瓨鍦�");
+    ErrorCode OVERTIME_WAGE_CONFIG_POST_EXISTS = new ErrorCode(1_030_005_028, "璇ュ矖浣嶅凡閰嶇疆鍔犵彮宸ヨ祫锛岃鍕块噸澶嶆坊鍔�");
 
     // ========== 鐝绠$悊 1-030-009-000 ==========
     ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "鐝涓嶅瓨鍦�");
diff --git a/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeCategoryEnum.java b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeCategoryEnum.java
new file mode 100644
index 0000000..1265b0a
--- /dev/null
+++ b/yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/HrmEmployeeCategoryEnum.java
@@ -0,0 +1,40 @@
+package cn.iocoder.yudao.module.hrm.enums;
+
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+
+import java.util.Arrays;
+
+/**
+ * 鍛樺伐绫诲埆鏋氫妇锛氱敤浜庡姞鐝伐璧勭殑宸紓鍖栨牳绠椼��
+ *
+ * <ul>
+ *     <li>鏅�氬憳宸ワ細鍔犵彮宸ヨ祫鎸夊矖浣嶉厤缃殑鍥哄畾鏃惰柂锛堥粯璁� 20 鍏�/灏忔椂锛夋牳绠�</li>
+ *     <li>鎶�宸ワ細鍔犵彮钖祫鎸� 1:1 鏍哥畻锛屼笌骞虫椂鏃惰柂涓�鑷达紙鍩烘湰宸ヨ祫 / 21.75 / 8锛�</li>
+ *     <li>绠$悊锛氫笉璁″姞鐝伐璧�</li>
+ * </ul>
+ */
+@Getter
+@RequiredArgsConstructor
+public enum HrmEmployeeCategoryEnum {
+
+    NORMAL(1, "鏅�氬憳宸�"),
+    TECHNICIAN(2, "鎶�宸�"),
+    MANAGER(3, "绠$悊");
+
+    private final Integer category;
+    private final String name;
+
+    public static HrmEmployeeCategoryEnum valueOfCategory(Integer category) {
+        if (category == null) {
+            return null;
+        }
+        return Arrays.stream(values()).filter(item -> item.category.equals(category)).findFirst().orElse(null);
+    }
+
+    public static String getName(Integer category) {
+        HrmEmployeeCategoryEnum value = valueOfCategory(category);
+        return value == null ? "" : value.name;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
index 8706fc4..383b2b5 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeRespVO.java
@@ -4,6 +4,7 @@
 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.api.storage.dto.StorageBlobRespDTO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
@@ -101,6 +102,11 @@
     @DictFormat("hrm_employee_status")
     private Integer employeeStatus;
 
+    @Schema(description = "鍛樺伐绫诲埆")
+    @ExcelProperty(value = "鍛樺伐绫诲埆", converter = DictConvert.class)
+    @DictFormat("hrm_employee_category")
+    private Integer employeeCategory;
+
     @Schema(description = "骞村亣浣欓锛堝ぉ锛�")
     @ExcelProperty("骞村亣浣欓")
     private BigDecimal annualLeaveBalance;
@@ -143,4 +149,7 @@
     @ExcelProperty("鍒涘缓鏃堕棿")
     private LocalDateTime createTime;
 
+    @Schema(description = "闄勪欢鍒楄〃")
+    private List<StorageBlobRespDTO> attachmentList;
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
index f0f0231..f642f86 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/employee/vo/HrmEmployeeSaveReqVO.java
@@ -78,6 +78,9 @@
     @NotNull(message = "鍛樺伐鐘舵�佷笉鑳戒负绌�")
     private Integer employeeStatus;
 
+    @Schema(description = "鍛樺伐绫诲埆锛�1-鏅�氬憳宸ワ紝2-鎶�宸ワ紝3-绠$悊", example = "1")
+    private Integer employeeCategory;
+
     @Schema(description = "骞村亣浣欓锛堝ぉ锛�", example = "5")
     private BigDecimal annualLeaveBalance;
 
@@ -119,4 +122,7 @@
     @Valid
     private List<HrmEmployeeEmergencyContactSaveReqVO> emergencyContacts;
 
+    @Schema(description = "闄勪欢 blobId 鍒楄〃", example = "[1, 2, 3]")
+    private List<Long> blobIds;
+
 }
\ No newline at end of file
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmOvertimeWageConfigController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmOvertimeWageConfigController.java
new file mode 100644
index 0000000..6b77184
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmOvertimeWageConfigController.java
@@ -0,0 +1,112 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary;
+
+import cn.hutool.core.collection.CollUtil;
+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.HrmOvertimeWageConfigRespVO;
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmOvertimeWageConfigSaveReqVO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import cn.iocoder.yudao.module.hrm.service.salary.HrmOvertimeWageConfigService;
+import cn.iocoder.yudao.module.system.api.dept.PostApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
+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.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.stream.Collectors;
+
+import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+
+@Tag(name = "绠$悊鍚庡彴 - HRM 鍔犵彮宸ヨ祫绠$悊")
+@RestController
+@RequestMapping("/hrm/salary/overtime-wage")
+@Validated
+public class HrmOvertimeWageConfigController {
+
+    @Resource
+    private HrmOvertimeWageConfigService overtimeWageConfigService;
+    @Resource
+    private PostApi postApi;
+
+    @PostMapping("/create")
+    @Operation(summary = "鍒涘缓宀椾綅鍔犵彮宸ヨ祫閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:create')")
+    public CommonResult<Long> createOvertimeWageConfig(@Valid @RequestBody HrmOvertimeWageConfigSaveReqVO reqVO) {
+        return success(overtimeWageConfigService.createOvertimeWageConfig(
+                BeanUtils.toBean(reqVO, HrmOvertimeWageConfigDO.class)));
+    }
+
+    @PutMapping("/update")
+    @Operation(summary = "鏇存柊宀椾綅鍔犵彮宸ヨ祫閰嶇疆")
+    @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:update')")
+    public CommonResult<Boolean> updateOvertimeWageConfig(@Valid @RequestBody HrmOvertimeWageConfigSaveReqVO reqVO) {
+        overtimeWageConfigService.updateOvertimeWageConfig(BeanUtils.toBean(reqVO, HrmOvertimeWageConfigDO.class));
+        return success(true);
+    }
+
+    @DeleteMapping("/delete")
+    @Operation(summary = "鍒犻櫎宀椾綅鍔犵彮宸ヨ祫閰嶇疆")
+    @Parameter(name = "id", description = "閰嶇疆缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:delete')")
+    public CommonResult<Boolean> deleteOvertimeWageConfig(@RequestParam("id") Long id) {
+        overtimeWageConfigService.deleteOvertimeWageConfig(id);
+        return success(true);
+    }
+
+    @GetMapping("/get")
+    @Operation(summary = "鑾峰緱宀椾綅鍔犵彮宸ヨ祫閰嶇疆")
+    @Parameter(name = "id", description = "閰嶇疆缂栧彿", required = true)
+    @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:query')")
+    public CommonResult<HrmOvertimeWageConfigRespVO> getOvertimeWageConfig(@RequestParam("id") Long id) {
+        return success(convert(overtimeWageConfigService.getOvertimeWageConfig(id)));
+    }
+
+    @GetMapping("/list")
+    @Operation(summary = "鑾峰緱宀椾綅鍔犵彮宸ヨ祫閰嶇疆鍒楄〃")
+    @PreAuthorize("@ss.hasPermission('hrm:overtime-wage:query')")
+    public CommonResult<List<HrmOvertimeWageConfigRespVO>> getOvertimeWageConfigList(
+            @RequestParam(value = "postId", required = false) Long postId,
+            @RequestParam(value = "status", required = false) Integer status) {
+        return success(convertList(overtimeWageConfigService.getOvertimeWageConfigList(postId, status)));
+    }
+
+    private HrmOvertimeWageConfigRespVO convert(HrmOvertimeWageConfigDO config) {
+        if (config == null) {
+            return null;
+        }
+        return convertList(Collections.singletonList(config)).get(0);
+    }
+
+    private List<HrmOvertimeWageConfigRespVO> convertList(List<HrmOvertimeWageConfigDO> list) {
+        List<HrmOvertimeWageConfigRespVO> voList = BeanUtils.toBean(list, HrmOvertimeWageConfigRespVO.class);
+        List<Long> postIds = voList.stream().map(HrmOvertimeWageConfigRespVO::getPostId)
+                .filter(Objects::nonNull).distinct().collect(Collectors.toList());
+        if (CollUtil.isNotEmpty(postIds)) {
+            Map<Long, PostRespDTO> postMap = postApi.getPostMap(postIds);
+            voList.forEach(vo -> {
+                PostRespDTO post = postMap.get(vo.getPostId());
+                if (post != null) {
+                    vo.setPostName(post.getName());
+                }
+            });
+        }
+        return voList;
+    }
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigRespVO.java
new file mode 100644
index 0000000..3acba1b
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigRespVO.java
@@ -0,0 +1,37 @@
+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 HrmOvertimeWageConfigRespVO {
+
+    @Schema(description = "閰嶇疆缂栧彿")
+    private Long id;
+
+    @Schema(description = "宀椾綅ID")
+    private Long postId;
+
+    @Schema(description = "宀椾綅鍚嶇О")
+    private String postName;
+
+    @Schema(description = "鍔犵彮鏃惰柂锛堝厓/灏忔椂锛�")
+    private BigDecimal hourlyRate;
+
+    @Schema(description = "姣忔湀鏈�澶у姞鐝椂闀夸笂闄愶紙灏忔椂锛夛紝涓虹┖琛ㄧず涓嶉檺")
+    private BigDecimal maxOvertimeHours;
+
+    @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/HrmOvertimeWageConfigSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigSaveReqVO.java
new file mode 100644
index 0000000..fc22c73
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmOvertimeWageConfigSaveReqVO.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.controller.admin.salary.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import jakarta.validation.constraints.DecimalMin;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.Data;
+
+import java.math.BigDecimal;
+
+@Schema(description = "绠$悊鍚庡彴 - 宀椾綅鍔犵彮宸ヨ祫閰嶇疆鏂板/淇敼 Request VO")
+@Data
+public class HrmOvertimeWageConfigSaveReqVO {
+
+    @Schema(description = "閰嶇疆缂栧彿", example = "1")
+    private Long id;
+
+    @Schema(description = "宀椾綅ID", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
+    @NotNull(message = "宀椾綅涓嶈兘涓虹┖")
+    private Long postId;
+
+    @Schema(description = "鍔犵彮鏃惰柂锛堝厓/灏忔椂锛夛紝榛樿 20", requiredMode = Schema.RequiredMode.REQUIRED, example = "20.00")
+    @NotNull(message = "鍔犵彮鏃惰柂涓嶈兘涓虹┖")
+    @DecimalMin(value = "0", message = "鍔犵彮鏃惰柂涓嶈兘灏忎簬 0")
+    private BigDecimal hourlyRate;
+
+    @Schema(description = "姣忔湀鏈�澶у姞鐝椂闀夸笂闄愶紙灏忔椂锛夛紝涓虹┖琛ㄧず涓嶉檺", example = "36.0")
+    @DecimalMin(value = "0", message = "鏈�澶у姞鐝椂闀夸笉鑳藉皬浜� 0")
+    private BigDecimal maxOvertimeHours;
+
+    @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/employee/HrmEmployeeDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
index b784f3d..dd4b92c 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/employee/HrmEmployeeDO.java
@@ -136,6 +136,13 @@
     private Integer employeeStatus;
 
     /**
+     * 鍛樺伐绫诲埆
+     *
+     * 1-鏅�氬憳宸ワ紝2-鎶�宸ワ紝3-绠$悊锛涚敤浜庡姞鐝伐璧勫樊寮傚寲鏍哥畻
+     */
+    private Integer employeeCategory;
+
+    /**
      * 骞村亣浣欓锛堝ぉ锛�
      */
     private BigDecimal annualLeaveBalance;
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmOvertimeWageConfigDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmOvertimeWageConfigDO.java
new file mode 100644
index 0000000..b414564
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmOvertimeWageConfigDO.java
@@ -0,0 +1,64 @@
+package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
+
+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;
+
+/**
+ * 宀椾綅鍔犵彮宸ヨ祫閰嶇疆 DO
+ *
+ * <p>鐢ㄤ簬銆屽姞鐝伐璧勭鐞嗐�嶏細涓嶅悓宀椾綅锛堝伐绉嶏級閰嶇疆涓嶅悓鐨勫姞鐝椂钖笌鏈�澶у姞鐝椂闀夸笂闄愩��
+ * 鏍哥畻鏃舵櫘閫氬憳宸ユ寜姝ら厤缃殑鍥哄畾鏃惰柂璁″姞鐝伐璧勩��
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_overtime_wage_config")
+@KeySequence("hrm_overtime_wage_config_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmOvertimeWageConfigDO extends BaseDO {
+
+    /**
+     * 閰嶇疆ID
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 宀椾綅ID
+     *
+     * 鍏宠仈 PostDO#getId()
+     */
+    private Long postId;
+
+    /**
+     * 鍔犵彮鏃惰柂锛堝厓/灏忔椂锛夛紝榛樿 20
+     */
+    private BigDecimal hourlyRate;
+
+    /**
+     * 姣忔湀鏈�澶у姞鐝椂闀夸笂闄愶紙灏忔椂锛�
+     *
+     * 涓虹┖琛ㄧず涓嶉檺鍒�
+     */
+    private BigDecimal maxOvertimeHours;
+
+    /**
+     * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
+     */
+    private Integer status;
+
+    /**
+     * 澶囨敞
+     */
+    private String remark;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmOvertimeWageConfigMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmOvertimeWageConfigMapper.java
new file mode 100644
index 0000000..5bc49c8
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmOvertimeWageConfigMapper.java
@@ -0,0 +1,39 @@
+package cn.iocoder.yudao.module.hrm.dal.mysql.salary;
+
+import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
+import cn.iocoder.yudao.framework.mybatis.core.query.LambdaQueryWrapperX;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+
+/**
+ * 宀椾綅鍔犵彮宸ヨ祫閰嶇疆 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmOvertimeWageConfigMapper extends BaseMapperX<HrmOvertimeWageConfigDO> {
+
+    default List<HrmOvertimeWageConfigDO> selectListByCondition(Long postId, Integer status) {
+        return selectList(new LambdaQueryWrapperX<HrmOvertimeWageConfigDO>()
+                .eqIfPresent(HrmOvertimeWageConfigDO::getPostId, postId)
+                .eqIfPresent(HrmOvertimeWageConfigDO::getStatus, status)
+                .orderByDesc(HrmOvertimeWageConfigDO::getId));
+    }
+
+    default HrmOvertimeWageConfigDO selectByPostId(Long postId) {
+        return selectOne(HrmOvertimeWageConfigDO::getPostId, postId);
+    }
+
+    /**
+     * 鏌ヨ澶氫釜宀椾綅涓嬨�屽惎鐢ㄣ�嶇殑鍔犵彮宸ヨ祫閰嶇疆锛岀敤浜庤柂閰牳绠楁椂鎸夊憳宸ュ矖浣嶅尮閰嶃��
+     */
+    default List<HrmOvertimeWageConfigDO> selectEnabledListByPostIds(Collection<Long> postIds) {
+        return selectList(new LambdaQueryWrapperX<HrmOvertimeWageConfigDO>()
+                .inIfPresent(HrmOvertimeWageConfigDO::getPostId, postIds)
+                .eq(HrmOvertimeWageConfigDO::getStatus, 0));
+    }
+
+}
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 eefe7a7..92a709e 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
@@ -31,6 +31,8 @@
 import cn.iocoder.yudao.module.system.api.dept.dto.PostRespDTO;
 import cn.iocoder.yudao.module.system.api.permission.RoleApi;
 import cn.iocoder.yudao.module.system.api.permission.dto.RoleRespDTO;
+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.AdminUserCreateReqDTO;
 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserUpdateReqDTO;
@@ -83,6 +85,8 @@
     @Resource
     private RoleApi roleApi;
     @Resource
+    private StorageAttachmentApi storageAttachmentApi;
+    @Resource
     @Lazy // 鑷敞鍏ラ渶寤惰繜鍔犺浇锛屽惁鍒� bean 琚唬鐞嗗寘瑁呭悗娉ㄥ叆鍘熷鐗堟湰瀵艰嚧鍚姩澶辫触
     private HrmEmployeeService self;
 
@@ -129,6 +133,8 @@
         }
         employeeMapper.insert(employee);
         Long employeeId = employee.getId();
+        // 缁戝畾闄勪欢
+        storageAttachmentApi.bindAttachments("file", "hrm_employee", employeeId, createReqVO.getBlobIds());
         // 7. 鍚屾娣诲姞鏁欒偛缁忓巻
         if (CollUtil.isNotEmpty(createReqVO.getEducations())) {
             createReqVO.getEducations().forEach(educationVO -> {
@@ -210,6 +216,8 @@
         syncWorkHistoryList(employeeId, updateReqVO.getWorkHistories());
         // 鍚屾绱ф�ヨ仈绯讳汉
         syncEmergencyContactList(employeeId, updateReqVO.getEmergencyContacts());
+        // 鏇存柊闄勪欢
+        storageAttachmentApi.updateAttachments("file", "hrm_employee", employeeId, updateReqVO.getBlobIds());
     }
 
     private void syncEducationList(Long employeeId, List<HrmEmployeeEducationSaveReqVO> newList) {
@@ -257,6 +265,8 @@
         educationMapper.deleteByEmployeeId(id);
         workHistoryMapper.deleteByEmployeeId(id);
         emergencyContactMapper.deleteByEmployeeId(id);
+        // 鍒犻櫎闄勪欢
+        storageAttachmentApi.deleteAttachmentsByRecord("hrm_employee", id);
         // 鍒犻櫎鍛樺伐
         employeeMapper.deleteById(id);
     }
@@ -311,6 +321,8 @@
                 respVO.setRoleNames(roles.stream().map(RoleRespDTO::getName).collect(Collectors.joining(",")));
             }
         }
+        // 濉厖闄勪欢
+        respVO.setAttachmentList(storageAttachmentApi.listAttachments("hrm_employee", id));
         return respVO;
     }
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigService.java
new file mode 100644
index 0000000..34e380d
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigService.java
@@ -0,0 +1,24 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+
+import java.util.List;
+
+/**
+ * 宀椾綅鍔犵彮宸ヨ祫閰嶇疆 Service
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+public interface HrmOvertimeWageConfigService {
+
+    Long createOvertimeWageConfig(HrmOvertimeWageConfigDO config);
+
+    void updateOvertimeWageConfig(HrmOvertimeWageConfigDO config);
+
+    void deleteOvertimeWageConfig(Long id);
+
+    HrmOvertimeWageConfigDO getOvertimeWageConfig(Long id);
+
+    List<HrmOvertimeWageConfigDO> getOvertimeWageConfigList(Long postId, Integer status);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigServiceImpl.java
new file mode 100644
index 0000000..f7f035e
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmOvertimeWageConfigServiceImpl.java
@@ -0,0 +1,83 @@
+package cn.iocoder.yudao.module.hrm.service.salary;
+
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmOvertimeWageConfigMapper;
+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.OVERTIME_WAGE_CONFIG_NOT_EXISTS;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.OVERTIME_WAGE_CONFIG_POST_EXISTS;
+
+/**
+ * 宀椾綅鍔犵彮宸ヨ祫閰嶇疆 Service 瀹炵幇绫�
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Service
+@Validated
+public class HrmOvertimeWageConfigServiceImpl implements HrmOvertimeWageConfigService {
+
+    /**
+     * 榛樿鍔犵彮鏃惰柂锛堝厓/灏忔椂锛�
+     */
+    private static final BigDecimal DEFAULT_HOURLY_RATE = new BigDecimal("20");
+
+    @Resource
+    private HrmOvertimeWageConfigMapper overtimeWageConfigMapper;
+
+    @Override
+    public Long createOvertimeWageConfig(HrmOvertimeWageConfigDO config) {
+        validatePostUnique(null, config.getPostId());
+        // 鏈~鍐欐椂钖椂锛屼娇鐢ㄩ粯璁� 20 鍏�/灏忔椂
+        if (config.getHourlyRate() == null) {
+            config.setHourlyRate(DEFAULT_HOURLY_RATE);
+        }
+        overtimeWageConfigMapper.insert(config);
+        return config.getId();
+    }
+
+    @Override
+    public void updateOvertimeWageConfig(HrmOvertimeWageConfigDO config) {
+        validateExists(config.getId());
+        validatePostUnique(config.getId(), config.getPostId());
+        overtimeWageConfigMapper.updateById(config);
+    }
+
+    @Override
+    public void deleteOvertimeWageConfig(Long id) {
+        validateExists(id);
+        overtimeWageConfigMapper.deleteById(id);
+    }
+
+    @Override
+    public HrmOvertimeWageConfigDO getOvertimeWageConfig(Long id) {
+        return overtimeWageConfigMapper.selectById(id);
+    }
+
+    @Override
+    public List<HrmOvertimeWageConfigDO> getOvertimeWageConfigList(Long postId, Integer status) {
+        return overtimeWageConfigMapper.selectListByCondition(postId, status);
+    }
+
+    private void validateExists(Long id) {
+        if (id == null || overtimeWageConfigMapper.selectById(id) == null) {
+            throw exception(OVERTIME_WAGE_CONFIG_NOT_EXISTS);
+        }
+    }
+
+    private void validatePostUnique(Long id, Long postId) {
+        if (postId == null) {
+            return;
+        }
+        HrmOvertimeWageConfigDO config = overtimeWageConfigMapper.selectByPostId(postId);
+        if (config != null && !config.getId().equals(id)) {
+            throw exception(OVERTIME_WAGE_CONFIG_POST_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 6cf8b19..5f0019e 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
@@ -32,6 +32,11 @@
 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.hutool.core.util.StrUtil;
+import cn.hutool.json.JSONUtil;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmOvertimeWageConfigDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmOvertimeWageConfigMapper;
+import cn.iocoder.yudao.module.hrm.enums.HrmEmployeeCategoryEnum;
 import cn.iocoder.yudao.module.hrm.enums.HrmOvertimeTypeEnum;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
@@ -97,6 +102,8 @@
     private HrmOvertimeApplicationMapper overtimeApplicationMapper;
     @Resource
     private HrmSalaryStructureMapper salaryStructureMapper;
+    @Resource
+    private HrmOvertimeWageConfigMapper overtimeWageConfigMapper;
     @Resource
     private HrmLeaveTypeConfigMapper leaveTypeConfigMapper;
     @Resource
@@ -371,8 +378,16 @@
     }
 
     /**
-     * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇枫�傚皬鏃跺伐璧勬寜鍩烘湰宸ヨ祫 / 21.75 / 8 璁$畻锛�
-     * 宸ヤ綔鏃ラ粯璁� 1.5 鍊嶃�佷紤鎭棩榛樿 2 鍊嶃�佹硶瀹氳妭鍋囨棩榛樿 3 鍊嶏紝鍧囧彲鐢辫柂閰粨鏋勮鐩栥��
+     * 姹囨�诲綋鏈堝鎵归�氳繃鐨勫姞鐝敵璇凤紝璁$畻鍔犵彮宸ヨ祫涓庡姞鐝椂闀裤��
+     *
+     * <p>鎸夈�屽憳宸ョ被鍒�嶅樊寮傚寲鏍哥畻锛�
+     * <ul>
+     *     <li>鎶�宸ワ細鍔犵彮钖祫鎸� 1:1 鏍哥畻锛屼笌骞虫椂鏃惰柂涓�鑷达紙鍩烘湰宸ヨ祫 / 21.75 / 8锛夛紱</li>
+     *     <li>鏅�氬憳宸ワ細鎸夊矖浣嶉厤缃殑鍥哄畾鍔犵彮鏃惰柂锛堥粯璁� 20 鍏�/灏忔椂锛夋牳绠楋紱</li>
+     *     <li>绠$悊锛氫笉璁″姞鐝伐璧勶紱</li>
+     * </ul>
+     * 鍛戒腑宀椾綅閰嶇疆鏃讹紝鎸夐厤缃殑鏈�澶у姞鐝椂闀夸笂闄愬鍔犵彮鏃堕暱灏侀《锛�
+     * 鏈懡涓换浣曞矖浣嶉厤缃笖闈炴妧宸�/绠$悊鏃讹紝鍥為��鏃ч�昏緫锛堣柂閰粨鏋勫�嶇巼 / 鍔犵彮绫诲瀷榛樿鍊嶇巼锛夈��
      */
     private OvertimeSalary calculateOvertimeSalary(HrmEmployeeDO employee, HrmEmployeeSalaryDO employeeSalary,
                                                     String period, BigDecimal baseSalary) {
@@ -385,37 +400,109 @@
         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)
+        // 绠$悊绫诲埆涓嶈鍔犵彮宸ヨ祫
+        HrmEmployeeCategoryEnum category = HrmEmployeeCategoryEnum.valueOfCategory(employee.getEmployeeCategory());
+        if (category == HrmEmployeeCategoryEnum.MANAGER) {
+            return new OvertimeSalary(BigDecimal.ZERO, BigDecimal.ZERO);
+        }
+        // 鍛樺伐宀椾綅鍛戒腑鐨勫姞鐝伐璧勯厤缃紙鍙栨椂钖渶楂樸�佷笂闄愭渶瀹界殑涓�鏉★級
+        OvertimeWageRule wageRule = resolveOvertimeWageRule(employee);
+        // 骞虫椂鏃惰柂 = 鍩烘湰宸ヨ祫 / 21.75 / 8
+        BigDecimal baseHourlySalary = 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;
+        if (category == HrmEmployeeCategoryEnum.TECHNICIAN || wageRule != null) {
+            // 鎶�宸ワ細1:1锛堜笌骞虫椂鏃惰柂涓�鑷达級锛涙櫘閫氬憳宸ュ懡涓厤缃細鎸夐厤缃浐瀹氭椂钖紝鍧囦笉鍖哄垎鍔犵彮绫诲瀷鍊嶇巼
+            BigDecimal hourlySalary = category == HrmEmployeeCategoryEnum.TECHNICIAN
+                    ? baseHourlySalary : wageRule.hourlyRate();
+            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;
+                }
+                totalHours = totalHours.add(hours);
             }
-            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));
+            // 鎸夊矖浣嶉厤缃皝椤跺姞鐝椂闀�
+            BigDecimal paidHours = wageRule != null && wageRule.maxOvertimeHours() != null
+                    && totalHours.compareTo(wageRule.maxOvertimeHours()) > 0
+                    ? wageRule.maxOvertimeHours() : totalHours;
+            totalPay = hourlySalary.multiply(paidHours);
+        } else {
+            // 鍏煎鏃ч�昏緫锛氭棤宀椾綅閰嶇疆銆佷笖闈炴妧宸�/绠$悊
+            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();
+            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(baseHourlySalary.multiply(hours).multiply(multiplier));
+            }
         }
         return new OvertimeSalary(totalHours.setScale(1, RoundingMode.HALF_UP),
                 totalPay.setScale(2, RoundingMode.HALF_UP));
     }
 
+    /**
+     * 瑙f瀽鍛樺伐宀椾綅瀵瑰簲鐨勫姞鐝伐璧勮鍒欙細鏃惰柂鍙栧懡涓厤缃腑鐨勬渶澶у�硷紱
+     * 涓婇檺鍙栤�滄渶瀹解�濓紙瀛樺湪鏈缃笂闄愮殑閰嶇疆鍒欒涓轰笉闄愶紝鍚﹀垯鍙栨渶澶у�硷級銆�
+     */
+    private OvertimeWageRule resolveOvertimeWageRule(HrmEmployeeDO employee) {
+        if (StrUtil.isEmpty(employee.getPostIds())) {
+            return null;
+        }
+        List<Long> postIds;
+        try {
+            postIds = JSONUtil.toList(employee.getPostIds(), Long.class);
+        } catch (Exception ex) {
+            return null;
+        }
+        if (CollUtil.isEmpty(postIds)) {
+            return null;
+        }
+        List<HrmOvertimeWageConfigDO> configs = overtimeWageConfigMapper.selectEnabledListByPostIds(postIds);
+        if (CollUtil.isEmpty(configs)) {
+            return null;
+        }
+        BigDecimal hourlyRate = null;
+        BigDecimal maxHours = null;
+        boolean unlimited = false;
+        for (HrmOvertimeWageConfigDO config : configs) {
+            if (config.getHourlyRate() != null
+                    && (hourlyRate == null || config.getHourlyRate().compareTo(hourlyRate) > 0)) {
+                hourlyRate = config.getHourlyRate();
+            }
+            if (config.getMaxOvertimeHours() == null) {
+                unlimited = true;
+            } else if (!unlimited && (maxHours == null || config.getMaxOvertimeHours().compareTo(maxHours) > 0)) {
+                maxHours = config.getMaxOvertimeHours();
+            }
+        }
+        if (hourlyRate == null) {
+            return null;
+        }
+        return new OvertimeWageRule(hourlyRate, unlimited ? null : maxHours);
+    }
+
+    private record OvertimeWageRule(BigDecimal hourlyRate, BigDecimal maxOvertimeHours) {}
+
     private record OvertimeSalary(BigDecimal hours, BigDecimal pay) {}
 
     private record ScheduleStat(int scheduledDays, BigDecimal totalWorkHours) {}
diff --git a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
index 5691d89..d23c872 100644
--- a/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
+++ b/yudao-module-system/src/main/java/cn/iocoder/yudao/module/system/enums/storage/StorageRecordTypeEnum.java
@@ -55,6 +55,7 @@
     HRM_RESIGNATION_APPLICATION("hrm_resignation_application", "HRM 绂昏亴鐢宠"),
     HRM_ATTENDANCE_EXCEPTION("hrm_attendance_exception", "HRM 鑰冨嫟寮傚父"),
     HRM_EMPLOYEE_CONTRACT("hrm_employee_contract", "HRM 鍛樺伐鍚堝悓"),
+    HRM_EMPLOYEE("hrm_employee", "HRM 鍛樺伐"),
 
     // ===== 鍞悗 =====
     AFTER_SALE_TICKET("aftersales_after_sale_ticket", "鍞悗宸ュ崟"),

--
Gitblit v1.9.3