From b5ca4fa6acd8c7ea8ed1400b133eb2eb6b5cb8ba Mon Sep 17 00:00:00 2001
From: gaoluyang <2820782392@qq.com>
Date: 星期三, 14 十月 2026 15:22:16 +0800
Subject: [PATCH] feat(hrm): 社保公积金方案支持按部门绑定,修比例输入精度

---
 sql/mysql/hrm_social_security_scheme_dept.sql                                                                               |   43 +++
 docs/hrm_social_security_scheme_dept_frontend_integration.md                                                                |   93 ++++++++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java    |   14 +
 yudao-module-hrm-api/src/main/java/cn/iocoder/yudao/module/hrm/enums/ErrorCodeConstants.java                                |    1 
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java   |   52 ++++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeDeptMapper.java          |   58 +++++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java             |   18 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java               |   45 +++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java |   11 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java           |  157 +++++++++++++
 docs/sql/config_export_all_20260928.sql                                                                                     |   18 +
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDeptDO.java         |   42 +++
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSalaryCalculationServiceImpl.java              |   64 ++++-
 13 files changed, 580 insertions(+), 36 deletions(-)

diff --git a/docs/hrm_social_security_scheme_dept_frontend_integration.md b/docs/hrm_social_security_scheme_dept_frontend_integration.md
new file mode 100644
index 0000000..7308f33
--- /dev/null
+++ b/docs/hrm_social_security_scheme_dept_frontend_integration.md
@@ -0,0 +1,93 @@
+# 绀句繚鍏Н閲戞柟妗堟敼涓恒�屾寜閮ㄩ棬缁戝畾銆� - 鍓嶇鑱旇皟鏂规
+
+## 鍙樻洿鑳屾櫙
+
+鍘熸潵绀句繚鍏Н閲戞柟妗堝彧鑳戒竴涓憳宸ヤ竴涓憳宸ュ湴鍦ㄣ�屽憳宸ョぞ淇濆叕绉噾妗f銆嶉噷缁戝畾锛屽嚑鐧惧彿浜洪�愪釜缁存姢涓嶇幇瀹炪��
+鐜板湪鏂规鏈韩鍙互鎸傘�岄�傜敤閮ㄩ棬銆嶏紝钖祫鏍哥畻鏃舵寜鍛樺伐鎵�鍦ㄩ儴闂ㄥ彇鏂规锛涘師鏉ョ殑鍛樺伐妗f淇濈暀浣滀釜鍒鐩栥��
+
+## 娑夊強椤甸潰
+
+- 绀句繚鍏Н閲戞柟妗堬紙`/hrm/salary/social-security-scheme`锛夛細琛ㄥ崟鏂板銆岄�傜敤閮ㄩ棬銆嶃�岄粯璁ょぞ淇濆熀鏁般�嶃�岄粯璁ゅ叕绉噾鍩烘暟銆嶏紝鍒楄〃鏂板銆岄�傜敤閮ㄩ棬銆嶅垪
+- 钖祫鏍哥畻锛坄/hrm/salary/calculation`锛夛細鏃犻渶鏀归�狅紝鎺ュ彛鍏ュ弬鏈潵灏辨湁 `deptId`锛岃涓鸿嚜鍔ㄧ敓鏁�
+- 鍛樺伐绀句繚鍏Н閲戞。妗堬細涓嶆敼锛岀户缁綔涓轰釜鍒鐩栧叆鍙�
+
+## API
+
+| 鏂规硶 | 璺緞 | 璇存槑 |
+|------|------|------|
+| POST | /hrm/salary/social-security-scheme/create | 鏂板鏂规锛屽彲甯﹂�傜敤閮ㄩ棬涓庨粯璁ゅ熀鏁� |
+| PUT | /hrm/salary/social-security-scheme/update | 淇敼鏂规锛岄�傜敤閮ㄩ棬涓烘暣缁勬浛鎹紙浼犵┖鏁扮粍鍗宠В闄ゅ叏閮ㄩ儴闂級 |
+| GET | /hrm/salary/social-security-scheme/get | 璇︽儏锛岃繑鍥� `deptIds` / `deptNames` |
+| GET | /hrm/salary/social-security-scheme/list | 鍒楄〃锛岃繑鍥� `deptIds` / `deptNames` |
+| GET | /hrm/salary/calculation/preview | 璇曠畻锛屽叆鍙� `period` + 鍙�� `deptId` |
+
+**璇锋眰鍙傛暟鏂板锛坈reate / update锛夛細**
+
+| 鍙傛暟 | 绫诲瀷 | 蹇呭~ | 璇存槑 |
+|------|------|------|------|
+| deptIds | Long[] | 鍚� | 閫傜敤閮ㄩ棬ID鍒楄〃锛涚┖鏁扮粍鎴栦笉浼� = 涓嶉檺瀹氶儴闂紙鍏ㄥ眬榛樿鏂规锛� |
+| defaultSocialSecurityBase | Decimal | 鍚� | 榛樿绀句繚缂磋垂鍩烘暟锛涚暀绌哄垯鐢ㄥ憳宸ュ熀鏈伐璧� |
+| defaultHousingFundBase | Decimal | 鍚� | 榛樿鍏Н閲戠即璐瑰熀鏁帮紱鐣欑┖鎴� 0 鍒欑敤绀句繚鍩烘暟 |
+
+**鍝嶅簲瀛楁鏂板锛坓et / list锛夛細**
+
+| 瀛楁 | 绫诲瀷 | 璇存槑 |
+|------|------|------|
+| deptIds | Long[] | 閫傜敤閮ㄩ棬ID鍒楄〃锛屾湭缁戝畾杩斿洖 `[]` |
+| deptNames | String | 閫傜敤閮ㄩ棬鍚嶇О锛屽涓敤銆屻�併�嶈繛鎺ワ紱鏈粦瀹氳繑鍥炵┖涓层�傚悕绉拌В鏋愬け璐ユ椂鍥為�� `ID=xxx` |
+| defaultSocialSecurityBase | Decimal | 榛樿绀句繚缂磋垂鍩烘暟锛屽彲涓� null |
+| defaultHousingFundBase | Decimal | 榛樿鍏Н閲戠即璐瑰熀鏁帮紝鍙负 null |
+
+**鍝嶅簲绀轰緥锛�**
+
+```json
+{
+  "code": 0,
+  "data": {
+    "id": 3,
+    "name": "2026骞村畞澶忕ぞ淇濇爣鍑�",
+    "effectiveDate": "2026-01-01",
+    "deptIds": [119, 129],
+    "deptNames": "鐢熶骇閮ㄣ�佷粨鍌ㄩ儴",
+    "defaultSocialSecurityBase": 5023.00,
+    "defaultHousingFundBase": 0.00,
+    "status": 0
+  }
+}
+```
+
+**閮ㄩ棬鍐茬獊鎶ラ敊锛坈ode 1030005027锛夛細**
+
+```json
+{
+  "code": 1030005027,
+  "data": null,
+  "msg": "閮ㄩ棬銆愮敓浜ч儴銆戝凡缁戝畾鏂规銆�2025骞村畞澶忕ぞ淇濇爣鍑嗐�戯紝鏃犳硶鍐嶇粦瀹氬埌鏂规銆�2026骞村畞澶忕ぞ淇濇爣鍑嗐�戙�傝鍏堝湪鍘熸柟妗堜笂鍙栨秷璇ラ儴闂紝鎴栨敼閫夊叾瀹冮儴闂�"
+}
+```
+
+## 瀛楁灞曠ず瑙勫垯
+
+| 瀛楁 | 灞曠ず浣嶇疆 | 璇存槑 |
+|------|----------|------|
+| 閫傜敤閮ㄩ棬 | 鏂规琛ㄥ崟锛堝閫夋爲锛� | 涓嶉�夎〃绀哄叏灞�榛樿鏂规锛宲laceholder 宸插啓鏄庛�屽彧瀵规墍閫夐儴闂ㄦ湰绾х殑鍛樺伐鐢熸晥銆� |
+| 閫傜敤閮ㄩ棬 | 鏂规鍒楄〃 | 鐩存帴灞曠ず鍚庣 `deptNames`锛涗负绌烘椂鏄剧ず銆屽叏灞�榛樿銆嶆爣绛� |
+| 榛樿绀句繚鍩烘暟 / 榛樿鍏Н閲戝熀鏁� | 鏂规琛ㄥ崟 | `InputNumber`锛宍min: 0`銆乣precision: 2`锛屽厑璁哥暀绌� |
+| 绀句繚/鍏Н閲戞墸娆� | 鏍哥畻鍒楄〃銆佽瘯绠楀脊绐� | 鏃犻渶鏀瑰姩锛屽悗绔寜鏂颁紭鍏堢骇鍙栨暟 |
+
+## 涓氬姟瑙勫垯璇存槑
+
+| 鍦烘櫙 | 瑙勫垯 |
+|------|------|
+| 姣斾緥鏉ユ簮浼樺厛绾� | 鈶� 鍛樺伐绀句繚妗f鎸囧畾鐨勬柟妗� 鈶� 鍛樺伐鎵�鍦ㄩ儴闂ㄧ粦瀹氱殑鍚敤鏂规锛堢敓鏁堟棩鏈� 鈮� 鏍哥畻鏈熼棿锛屽彇鏈�鏂帮級 鈶� 鏈粦瀹氫换浣曢儴闂ㄧ殑鍚敤鏂规涓敓鏁堟棩鏈熸渶鏂拌�咃紙鍏ㄥ眬榛樿锛� 鈶� 閮芥病鏈夊垯鐢ㄤ唬鐮侀粯璁ゆ瘮渚� |
+| 缂磋垂鍩烘暟浼樺厛绾� | 绀句繚鍩烘暟锛氭。妗� 鈫� 鏂规榛樿绀句繚鍩烘暟 鈫� 鍛樺伐鍩烘湰宸ヨ祫锛涘叕绉噾鍩烘暟锛氭。妗� 鈫� 鏂规榛樿鍏Н閲戝熀鏁帮紙>0 鎵嶇敤锛夆啋 绀句繚鍩烘暟 |
+| 閮ㄩ棬瑕嗙洊鑼冨洿 | 鍙鐩栨墍閫夐儴闂�**鏈骇**鐨勫憳宸ワ紝涓嶅惈瀛愰儴闂� |
+| 涓�涓儴闂ㄧ殑褰掑睘 | 涓�涓儴闂ㄥ悓鏃跺彧鑳界粦瀹氫竴涓柟妗堬紱淇濆瓨鏃惰嫢璇ラ儴闂ㄥ凡琚埆鐨勬柟妗堝崰鐢紝杩斿洖涓婇潰閭f潯绮剧‘鎶ラ敊 |
+| 鍒犻櫎鏂规 | 鏂规鍒犻櫎鏃跺叾閮ㄩ棬鍏宠仈涓�骞惰В闄わ紝閮ㄩ棬闅忎箣绌洪棽 |
+
+## 娉ㄦ剰浜嬮」
+
+- 鏂规鐨勩�岄�傜敤閮ㄩ棬銆嶆槸**鏁寸粍鏇挎崲**璇箟锛氱紪杈戞椂瑕佹妸褰撳墠缁戝畾鐨勯儴闂ㄥ叏閮ㄥ洖鏄惧埌澶氶�夋閲屽啀鎻愪氦锛屼笉鑳藉彧鎻愪氦鏈鏂板鐨勯儴闂ㄣ��
+- 瀛橀噺鏂规榛樿娌℃湁缁戝畾浠讳綍閮ㄩ棬锛屽洜姝ら兘鏄�屽叏灞�榛樿銆嶅�欓�夛紱姝ゆ椂鏍哥畻缁撴灉涓庢敼閫犲墠瀹屽叏涓�鑷达紙涓囬�� 23 鍚嶅憳宸ュ彛寰勫凡閫愯鏍稿涓�鑷达級锛岃鐢熸晥蹇呴』鍏堢粰鏂规濉�岄粯璁ょ即璐瑰熀鏁般�嶅苟鎶婃柟妗堢粦鍒伴儴闂ㄣ��
+- 鍛樺伐绀句繚鍏Н閲戞。妗堥噷宸叉寚瀹氭柟妗堢殑鍛樺伐锛屾案杩滀紭鍏堣蛋妗f锛屾柟妗堢粦瀹氶儴闂ㄤ笉浼氭敼鍙樹粬浠殑閲戦銆�
+- 姣斾緥杈撳叆妗嗚蛋鐨勬槸銆岀櫨鍒嗘瘮鏄剧ず銆佸皬鏁板瓨鍌ㄣ�嶏細鐣岄潰濉� 8.5 琛ㄧず 8.5%锛岃惤搴� 0.085锛涙樉绀轰繚鐣� 2 浣嶇櫨鍒嗘瘮灏忔暟锛屼笌鏁版嵁搴� `decimal(5,4)` 绮惧害涓�涓�瀵瑰簲銆備釜绋庣◣鐜囬厤缃〉鐨勮〃鍗曚笌鍒楄〃鍚屽彛寰勩��
diff --git a/docs/sql/config_export_all_20260928.sql b/docs/sql/config_export_all_20260928.sql
index 993a853..92c7e74 100644
--- a/docs/sql/config_export_all_20260928.sql
+++ b/docs/sql/config_export_all_20260928.sql
@@ -3674,6 +3674,8 @@
   `maternity_ratio` decimal(5,4) NOT NULL DEFAULT '0.0050' COMMENT '鐢熻偛淇濋櫓浼佷笟缂寸撼姣斾緥',
   `housing_fund_company_ratio` decimal(5,4) NOT NULL DEFAULT '0.1200' COMMENT '鍏Н閲戜紒涓氱即绾虫瘮渚�',
   `housing_fund_personal_ratio` decimal(5,4) NOT NULL DEFAULT '0.1200' COMMENT '鍏Н閲戜釜浜虹即绾虫瘮渚�',
+  `default_social_security_base` decimal(12,2) DEFAULT NULL COMMENT '榛樿绀句繚缂磋垂鍩烘暟锛堢┖鍒欑敤鍛樺伐鍩烘湰宸ヨ祫锛�',
+  `default_housing_fund_base` decimal(12,2) DEFAULT NULL COMMENT '榛樿鍏Н閲戠即璐瑰熀鏁帮紙绌烘垨 0 鍒欑敤绀句繚缂磋垂鍩烘暟锛�',
   `status` tinyint NOT NULL DEFAULT '0' COMMENT '鐘舵�侊細0-鍚敤锛�1-绂佺敤',
   `remark` varchar(500) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '澶囨敞',
   `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
@@ -3685,6 +3687,22 @@
   KEY `idx_effective_date` (`effective_date`) USING BTREE
 ) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='HRM绀句繚鍏Н閲戞柟妗堣〃';
 
+-- ============ 琛細hrm_social_security_scheme_dept锛堜粎 DDL锛屾棤鏁版嵁锛� ============
+DROP TABLE IF EXISTS `hrm_social_security_scheme_dept`;
+CREATE TABLE `hrm_social_security_scheme_dept` (
+  `id` bigint NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+  `scheme_id` bigint NOT NULL COMMENT '绀句繚鍏Н閲戞柟妗圛D',
+  `dept_id` bigint NOT NULL COMMENT '閫傜敤閮ㄩ棬ID',
+  `creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci DEFAULT '' COMMENT '鍒涘缓鑰�',
+  `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '鍒涘缓鏃堕棿',
+  `updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci 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`) USING BTREE,
+  KEY `idx_scheme_id` (`scheme_id`) USING BTREE,
+  KEY `idx_dept_id` (`dept_id`) USING BTREE
+) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci ROW_FORMAT=DYNAMIC COMMENT='HRM绀句繚鍏Н閲戞柟妗堥�傜敤閮ㄩ棬琛�';
+
 -- ============ 琛細hrm_tax_rate_config锛堜粎 DDL锛屾棤鏁版嵁锛� ============
 DROP TABLE IF EXISTS `hrm_tax_rate_config`;
 CREATE TABLE `hrm_tax_rate_config` (
diff --git a/sql/mysql/hrm_social_security_scheme_dept.sql b/sql/mysql/hrm_social_security_scheme_dept.sql
new file mode 100644
index 0000000..d67d522
--- /dev/null
+++ b/sql/mysql/hrm_social_security_scheme_dept.sql
@@ -0,0 +1,43 @@
+-- 绀句繚鍏Н閲戞柟妗堬細鏀逛负銆屾寜閮ㄩ棬缁戝畾銆�
+-- 1) 鏂规琛ㄥ鍔犻粯璁ょ即璐瑰熀鏁帮紙涓嶉�夐儴闂ㄧ殑鍛樺伐鎸夊畠璁$畻锛�
+-- 2) 鏂板銆屾柟妗� - 閫傜敤閮ㄩ棬銆嶅叧鑱旇〃锛堜竴涓柟妗堝彲缁戝畾澶氫釜閮ㄩ棬锛涗竴涓儴闂ㄥ彧鑳界粦瀹氫竴涓柟妗堬紝鍞竴鎬у湪鏈嶅姟灞傛牎楠岋級
+-- 璇存槑锛欰LTER TABLE ADD COLUMN 涓嶆敮鎸� IF NOT EXISTS锛岃繖閲屽厛鏌� information_schema 鍐嶅喅瀹氭槸鍚︽墽琛�
+
+SET @db := DATABASE();
+
+-- 1.1 榛樿绀句繚缂磋垂鍩烘暟
+SET @exists := (SELECT COUNT(*) FROM information_schema.columns
+                WHERE table_schema = @db AND table_name = 'hrm_social_security_scheme'
+                  AND column_name = 'default_social_security_base');
+SET @sql := IF(@exists = 0,
+    'ALTER TABLE `hrm_social_security_scheme` ADD COLUMN `default_social_security_base` decimal(12,2) DEFAULT NULL COMMENT ''榛樿绀句繚缂磋垂鍩烘暟锛堢┖鍒欑敤鍛樺伐鍩烘湰宸ヨ祫锛�'' AFTER `housing_fund_personal_ratio`',
+    'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 1.2 榛樿鍏Н閲戠即璐瑰熀鏁�
+SET @exists := (SELECT COUNT(*) FROM information_schema.columns
+                WHERE table_schema = @db AND table_name = 'hrm_social_security_scheme'
+                  AND column_name = 'default_housing_fund_base');
+SET @sql := IF(@exists = 0,
+    'ALTER TABLE `hrm_social_security_scheme` ADD COLUMN `default_housing_fund_base` decimal(12,2) DEFAULT NULL COMMENT ''榛樿鍏Н閲戠即璐瑰熀鏁帮紙绌烘垨 0 鍒欑敤绀句繚缂磋垂鍩烘暟锛�'' AFTER `default_social_security_base`',
+    'SELECT 1');
+PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt;
+
+-- 2. 鏂规 - 閫傜敤閮ㄩ棬鍏宠仈琛�
+CREATE TABLE IF NOT EXISTS `hrm_social_security_scheme_dept` (
+    `id`          bigint      NOT NULL AUTO_INCREMENT COMMENT '涓婚敭',
+    `scheme_id`   bigint      NOT NULL COMMENT '绀句繚鍏Н閲戞柟妗圛D',
+    `dept_id`     bigint      NOT NULL COMMENT '閫傜敤閮ㄩ棬ID',
+    `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`),
+    KEY `idx_scheme_id` (`scheme_id`),
+    KEY `idx_dept_id` (`dept_id`)
+) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '绀句繚鍏Н閲戞柟妗堥�傜敤閮ㄩ棬';
+
+-- 鍥炴粴锛堝闇�锛夛細
+-- ALTER TABLE `hrm_social_security_scheme` DROP COLUMN `default_social_security_base`, DROP COLUMN `default_housing_fund_base`;
+-- DROP TABLE IF EXISTS `hrm_social_security_scheme_dept`;
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 ec3dae4..f2fe5d7 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
@@ -124,6 +124,7 @@
     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 SOCIAL_SECURITY_SCHEME_DEPT_EXISTS = new ErrorCode(1_030_005_027, "閮ㄩ棬銆恵}銆戝凡缁戝畾鏂规銆恵}銆戯紝鏃犳硶鍐嶇粦瀹氬埌鏂规銆恵}銆戙�傝鍏堝湪鍘熸柟妗堜笂鍙栨秷璇ラ儴闂紝鎴栨敼閫夊叾瀹冮儴闂�");
 
     // ========== 鐝绠$悊 1-030-009-000 ==========
     ErrorCode SHIFT_NOT_EXISTS = new ErrorCode(1_030_009_000, "鐝涓嶅瓨鍦�");
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
index 0aad697..21b30a4 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/HrmSocialSecuritySchemeController.java
@@ -1,11 +1,14 @@
 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.HrmSocialSecuritySchemeRespVO;
 import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSocialSecuritySchemeSaveReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
 import cn.iocoder.yudao.module.hrm.service.salary.HrmSocialSecuritySchemeService;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
@@ -15,7 +18,10 @@
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
+import java.util.Collections;
 import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
 
@@ -28,20 +34,21 @@
     @Resource
     private HrmSocialSecuritySchemeService schemeService;
 
+    @Resource
+    private DeptApi deptApi;
+
     @PostMapping("/create")
     @Operation(summary = "鍒涘缓绀句繚鍏Н閲戞柟妗�")
     @PreAuthorize("@ss.hasPermission('hrm:social-security-scheme:create')")
     public CommonResult<Long> createScheme(@Valid @RequestBody HrmSocialSecuritySchemeSaveReqVO createReqVO) {
-        HrmSocialSecuritySchemeDO scheme = BeanUtils.toBean(createReqVO, HrmSocialSecuritySchemeDO.class);
-        return success(schemeService.createScheme(scheme));
+        return success(schemeService.createScheme(createReqVO));
     }
 
     @PutMapping("/update")
     @Operation(summary = "鏇存柊绀句繚鍏Н閲戞柟妗�")
     @PreAuthorize("@ss.hasPermission('hrm:social-security-scheme:update')")
     public CommonResult<Boolean> updateScheme(@Valid @RequestBody HrmSocialSecuritySchemeSaveReqVO updateReqVO) {
-        HrmSocialSecuritySchemeDO scheme = BeanUtils.toBean(updateReqVO, HrmSocialSecuritySchemeDO.class);
-        schemeService.updateScheme(scheme);
+        schemeService.updateScheme(updateReqVO);
         return success(true);
     }
 
@@ -60,7 +67,13 @@
     @PreAuthorize("@ss.hasPermission('hrm:social-security-scheme:query')")
     public CommonResult<HrmSocialSecuritySchemeRespVO> getScheme(@RequestParam("id") Long id) {
         HrmSocialSecuritySchemeDO scheme = schemeService.getScheme(id);
-        return success(BeanUtils.toBean(scheme, HrmSocialSecuritySchemeRespVO.class));
+        if (scheme == null) {
+            return success(null);
+        }
+        HrmSocialSecuritySchemeRespVO respVO = BeanUtils.toBean(scheme, HrmSocialSecuritySchemeRespVO.class);
+        // 濉厖閫傜敤閮ㄩ棬
+        fillDeptInfo(List.of(respVO));
+        return success(respVO);
     }
 
     @GetMapping("/list")
@@ -70,7 +83,34 @@
     public CommonResult<List<HrmSocialSecuritySchemeRespVO>> getSchemeList(
             @RequestParam(value = "status", required = false) Integer status) {
         List<HrmSocialSecuritySchemeDO> list = schemeService.getSchemeList(status);
-        return success(BeanUtils.toBean(list, HrmSocialSecuritySchemeRespVO.class));
+        List<HrmSocialSecuritySchemeRespVO> respList = BeanUtils.toBean(list, HrmSocialSecuritySchemeRespVO.class);
+        // 濉厖閫傜敤閮ㄩ棬
+        fillDeptInfo(respList);
+        return success(respList);
+    }
+
+    /**
+     * 濉厖鏂规鐨勯�傜敤閮ㄩ棬ID涓庨儴闂ㄥ悕绉�
+     */
+    private void fillDeptInfo(List<HrmSocialSecuritySchemeRespVO> list) {
+        if (CollUtil.isEmpty(list)) {
+            return;
+        }
+        List<Long> schemeIds = list.stream().map(HrmSocialSecuritySchemeRespVO::getId).toList();
+        Map<Long, List<Long>> schemeDeptIdMap = schemeService.getSchemeDeptIdMap(schemeIds);
+        List<Long> allDeptIds = schemeDeptIdMap.values().stream().flatMap(List::stream).distinct().toList();
+        Map<Long, DeptRespDTO> deptMap = CollUtil.isEmpty(allDeptIds)
+                ? Collections.emptyMap() : deptApi.getDeptMap(allDeptIds);
+        for (HrmSocialSecuritySchemeRespVO respVO : list) {
+            List<Long> deptIds = schemeDeptIdMap.getOrDefault(respVO.getId(), Collections.emptyList());
+            respVO.setDeptIds(deptIds);
+            respVO.setDeptNames(deptIds.stream()
+                    .map(deptId -> {
+                        DeptRespDTO dept = deptMap.get(deptId);
+                        return dept != null ? dept.getName() : "ID=" + deptId;
+                    })
+                    .collect(Collectors.joining("銆�")));
+        }
     }
 
 }
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java
index 9c2462d..314e101 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeRespVO.java
@@ -6,6 +6,7 @@
 import java.math.BigDecimal;
 import java.time.LocalDate;
 import java.time.LocalDateTime;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戞柟妗� Response VO")
 @Data
@@ -56,6 +57,19 @@
     @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", example = "0.12")
     private BigDecimal housingFundPersonalRatio;
 
+    // ========== 缂磋垂鍩烘暟 ==========
+    @Schema(description = "榛樿绀句繚缂磋垂鍩烘暟锛堢┖鍒欑敤鍛樺伐鍩烘湰宸ヨ祫锛�", example = "5023.00")
+    private BigDecimal defaultSocialSecurityBase;
+
+    @Schema(description = "榛樿鍏Н閲戠即璐瑰熀鏁帮紙绌烘垨 0 鍒欑敤绀句繚缂磋垂鍩烘暟锛�", example = "5023.00")
+    private BigDecimal defaultHousingFundBase;
+
+    @Schema(description = "閫傜敤閮ㄩ棬ID鍒楄〃锛堜负绌鸿〃绀哄叏灞�榛樿鏂规锛�", example = "[119,129]")
+    private List<Long> deptIds;
+
+    @Schema(description = "閫傜敤閮ㄩ棬鍚嶇О锛堝涓敤銆佸垎闅旓紝涓虹┖琛ㄧず鍏ㄥ眬榛樿锛�", example = "鐢熶骇閮ㄣ�佷粨鍌ㄩ儴")
+    private String deptNames;
+
     @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
     private Integer status;
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java
index 08099ab..0948a92 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/controller/admin/salary/vo/HrmSocialSecuritySchemeSaveReqVO.java
@@ -7,6 +7,7 @@
 
 import java.math.BigDecimal;
 import java.time.LocalDate;
+import java.util.List;
 
 @Schema(description = "绠$悊鍚庡彴 - 绀句繚鍏Н閲戞柟妗堟柊澧�/淇敼 Request VO")
 @Data
@@ -59,6 +60,16 @@
     @Schema(description = "鍏Н閲戜釜浜虹即绾虫瘮渚�", example = "0.12")
     private BigDecimal housingFundPersonalRatio;
 
+    // ========== 缂磋垂鍩烘暟 ==========
+    @Schema(description = "榛樿绀句繚缂磋垂鍩烘暟锛堢┖鍒欑敤鍛樺伐鍩烘湰宸ヨ祫锛�", example = "5023.00")
+    private BigDecimal defaultSocialSecurityBase;
+
+    @Schema(description = "榛樿鍏Н閲戠即璐瑰熀鏁帮紙绌烘垨 0 鍒欑敤绀句繚缂磋垂鍩烘暟锛�", example = "5023.00")
+    private BigDecimal defaultHousingFundBase;
+
+    @Schema(description = "閫傜敤閮ㄩ棬ID鍒楄〃锛堜负绌鸿〃绀哄叏灞�榛樿鏂规锛�", example = "[119,129]")
+    private List<Long> deptIds;
+
     @Schema(description = "鐘舵�侊細0-鍚敤锛�1-绂佺敤", example = "0")
     private Integer status;
 
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java
index 1962eeb..4744a71 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDO.java
@@ -1,7 +1,9 @@
 package cn.iocoder.yudao.module.hrm.dal.dataobject.salary;
 
 import cn.iocoder.yudao.framework.mybatis.core.dataobject.BaseDO;
+import com.baomidou.mybatisplus.annotation.FieldStrategy;
 import com.baomidou.mybatisplus.annotation.KeySequence;
+import com.baomidou.mybatisplus.annotation.TableField;
 import com.baomidou.mybatisplus.annotation.TableId;
 import com.baomidou.mybatisplus.annotation.TableName;
 import lombok.*;
@@ -94,6 +96,22 @@
      */
     private BigDecimal housingFundPersonalRatio;
 
+    // ========== 缂磋垂鍩烘暟 ==========
+    /**
+     * 榛樿绀句繚缂磋垂鍩烘暟锛堢┖鍒欑敤鍛樺伐鍩烘湰宸ヨ祫锛�
+     *
+     * updateStrategy=ALWAYS锛氭竻绌鸿瀛楁鏃惰鑳藉啓鍥� NULL
+     */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private BigDecimal defaultSocialSecurityBase;
+    /**
+     * 榛樿鍏Н閲戠即璐瑰熀鏁帮紙绌烘垨 0 鍒欑敤绀句繚缂磋垂鍩烘暟锛�
+     *
+     * updateStrategy 鍚� {@link #defaultSocialSecurityBase}
+     */
+    @TableField(updateStrategy = FieldStrategy.ALWAYS)
+    private BigDecimal defaultHousingFundBase;
+
     /**
      * 鐘舵�侊細0-鍚敤锛�1-绂佺敤
      */
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDeptDO.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDeptDO.java
new file mode 100644
index 0000000..fbca5b0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/dataobject/salary/HrmSocialSecuritySchemeDeptDO.java
@@ -0,0 +1,42 @@
+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.*;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗堥�傜敤閮ㄩ棬 DO
+ *
+ * 涓�涓柟妗堝彲缁戝畾澶氫釜閮ㄩ棬锛涗竴涓儴闂ㄥ彧鑳界粦瀹氫竴涓柟妗堬紙鍦� Service 灞傛牎楠岋級
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@TableName("hrm_social_security_scheme_dept")
+@KeySequence("hrm_social_security_scheme_dept_seq")
+@Data
+@EqualsAndHashCode(callSuper = true)
+@ToString(callSuper = true)
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+public class HrmSocialSecuritySchemeDeptDO extends BaseDO {
+
+    /**
+     * 涓婚敭
+     */
+    @TableId
+    private Long id;
+
+    /**
+     * 绀句繚鍏Н閲戞柟妗圛D
+     */
+    private Long schemeId;
+
+    /**
+     * 閫傜敤閮ㄩ棬ID
+     */
+    private Long deptId;
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeDeptMapper.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeDeptMapper.java
new file mode 100644
index 0000000..c0d61b0
--- /dev/null
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/dal/mysql/salary/HrmSocialSecuritySchemeDeptMapper.java
@@ -0,0 +1,58 @@
+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.HrmSocialSecuritySchemeDeptDO;
+import org.apache.ibatis.annotations.Mapper;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+/**
+ * 绀句繚鍏Н閲戞柟妗堥�傜敤閮ㄩ棬 Mapper
+ *
+ * @author 瓒呯骇绠$悊鍛�
+ */
+@Mapper
+public interface HrmSocialSecuritySchemeDeptMapper extends BaseMapperX<HrmSocialSecuritySchemeDeptDO> {
+
+    /**
+     * 鎸夋柟妗堟暣缁勬浛鎹細鍏堝垹闄よ鏂规鐨勫叏閮紙閫昏緫鍒犻櫎锛夊叧鑱旇
+     */
+    default void deleteBySchemeId(Long schemeId) {
+        delete(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .eq(HrmSocialSecuritySchemeDeptDO::getSchemeId, schemeId));
+    }
+
+    default List<HrmSocialSecuritySchemeDeptDO> selectListBySchemeId(Long schemeId) {
+        return selectList(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .eq(HrmSocialSecuritySchemeDeptDO::getSchemeId, schemeId));
+    }
+
+    default List<HrmSocialSecuritySchemeDeptDO> selectListBySchemeIds(Collection<Long> schemeIds) {
+        return selectList(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .inIfPresent(HrmSocialSecuritySchemeDeptDO::getSchemeId, schemeIds));
+    }
+
+    default List<HrmSocialSecuritySchemeDeptDO> selectListByDeptIds(Collection<Long> deptIds) {
+        return selectList(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .inIfPresent(HrmSocialSecuritySchemeDeptDO::getDeptId, deptIds));
+    }
+
+    default List<HrmSocialSecuritySchemeDeptDO> selectListByDeptId(Long deptId) {
+        return selectList(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .eq(HrmSocialSecuritySchemeDeptDO::getDeptId, deptId));
+    }
+
+    /**
+     * 鏌ヨ鍏ㄩ儴宸茬粦瀹氶儴闂ㄧ殑鏂规ID锛堢敤浜庡尯鍒嗐�岄儴闂ㄤ笓灞炴柟妗堛�嶄笌銆屽叏灞�榛樿鏂规銆嶏級
+     */
+    default Set<Long> selectAllBoundSchemeIds() {
+        return selectObjs(new LambdaQueryWrapperX<HrmSocialSecuritySchemeDeptDO>()
+                .select(HrmSocialSecuritySchemeDeptDO::getSchemeId))
+                .stream().map(obj -> (Long) obj).collect(Collectors.toSet());
+    }
+
+}
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..c2b563e 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
@@ -44,6 +44,7 @@
 import java.time.LocalDateTime;
 import java.time.YearMonth;
 import java.util.Collections;
+import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
 import java.util.Objects;
@@ -85,6 +86,8 @@
     private HrmEmployeeSocialSecurityMapper employeeSocialSecurityMapper;
     @Resource
     private HrmSocialSecuritySchemeMapper socialSecuritySchemeMapper;
+    @Resource
+    private HrmSocialSecuritySchemeService socialSecuritySchemeService;
     @Resource
     private HrmNoRedisDAO noRedisDAO;
     @Resource
@@ -140,6 +143,8 @@
                 .toList();
 
         // 2. 閬嶅巻鍛樺伐杩涜钖叕鏍哥畻
+        // 閮ㄩ棬 -> 鏂规 鐨勮В鏋愮粨鏋滃湪鏈鏍哥畻鍐呯紦瀛橈紝閬垮厤閫愬憳宸ラ噸澶嶆煡搴�
+        Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache = new HashMap<>();
         for (HrmEmployeeDO employee : employees) {
             // employeeId 鏄憳宸D锛岀敤浜庡叧鑱旇柂閰。妗堛�佺ぞ淇濇。妗堛�佹牳绠楄褰�
             Long employeeId = employee.getId();
@@ -165,11 +170,8 @@
 
             // 2.3 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
             HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
-            // 2.4 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
-            HrmSocialSecuritySchemeDO scheme = null;
-            if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
-                scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
-            }
+            // 2.4 鑾峰彇鏂规锛氬憳宸ユ。妗堟寚瀹� > 鎵�鍦ㄩ儴闂ㄧ粦瀹� > 鍏ㄥ眬榛樿
+            HrmSocialSecuritySchemeDO scheme = resolveScheme(socialSecurity, employee, effectiveDate, deptSchemeCache);
 
             // 2.5 鏋勫缓鏍哥畻璁板綍锛堝寘鍚墸闄よ绠楋紝閫氳繃鏂规鑾峰彇姣斾緥锛�
             HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
@@ -245,13 +247,22 @@
         BigDecimal totalIncome = baseSalary.add(performanceSalary).add(mealAllowance)
                 .add(transportAllowance).add(otherAllowance).add(overtimePay);
 
-        // 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鍛樺伐鍩烘湰宸ヨ祫
+        // 鑾峰彇绀句繚鍩烘暟锛氬憳宸ョぞ淇濇。妗� > 鏂规榛樿缂磋垂鍩烘暟 > 鍛樺伐鍩烘湰宸ヨ祫
         BigDecimal socialSecurityBase = (socialSecurity != null && socialSecurity.getSocialSecurityBase() != null)
-                ? socialSecurity.getSocialSecurityBase() : baseSalary;
+                ? socialSecurity.getSocialSecurityBase()
+                : (scheme != null && scheme.getDefaultSocialSecurityBase() != null
+                        ? scheme.getDefaultSocialSecurityBase() : baseSalary);
 
-        // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鍛樺伐鍩烘湰宸ヨ祫
-        BigDecimal housingFundBase = (socialSecurity != null && socialSecurity.getHousingFundBase() != null)
-                ? socialSecurity.getHousingFundBase() : baseSalary;
+        // 鑾峰彇鍏Н閲戝熀鏁帮細鍛樺伐绀句繚妗f > 鏂规榛樿鍏Н閲戝熀鏁帮紙澶т簬 0 鏃讹級> 绀句繚鍩烘暟
+        BigDecimal housingFundBase;
+        if (socialSecurity != null && socialSecurity.getHousingFundBase() != null) {
+            housingFundBase = socialSecurity.getHousingFundBase();
+        } else if (scheme != null && scheme.getDefaultHousingFundBase() != null
+                && scheme.getDefaultHousingFundBase().compareTo(BigDecimal.ZERO) > 0) {
+            housingFundBase = scheme.getDefaultHousingFundBase();
+        } else {
+            housingFundBase = socialSecurityBase;
+        }
 
         // 浠庢柟妗堜腑鑾峰彇缂寸撼姣斾緥锛堜紭鍏堜娇鐢ㄦ柟妗堜腑鐨勬瘮渚嬶紝鍚﹀垯浣跨敤榛樿姣斾緥锛�
         BigDecimal pensionPersonalRatio = (scheme != null && scheme.getPensionPersonalRatio() != null)
@@ -316,6 +327,30 @@
                 .totalWorkHours(totalWorkHours)
                 .status(0) // 寰呯‘璁�
                 .build();
+    }
+
+    /**
+     * 瑙f瀽鍛樺伐鏍哥畻鐢ㄧ殑绀句繚鍏Н閲戞柟妗�
+     *
+     * 浼樺厛绾э細鍛樺伐绀句繚妗f鎸囧畾鐨勬柟妗堬紙涓埆瑕嗙洊锛�> 鍛樺伐鎵�鍦ㄩ儴闂ㄧ粦瀹氱殑鏂规 > 鍏ㄥ眬榛樿鏂规銆�
+     * 閮藉彇涓嶅埌鏃惰繑鍥� null锛岀敱 {@link #buildSalaryCalculation} 浣跨敤榛樿姣斾緥甯搁噺銆�
+     *
+     * @param deptSchemeCache 鏈鏍哥畻鍐呫�岄儴闂� -> 鏂规銆嶇殑缂撳瓨锛堝厑璁� null 鍊硷紝鐢ㄤ簬缂撳瓨"璇ラ儴闂ㄦ棤鏂规"锛�
+     */
+    private HrmSocialSecuritySchemeDO resolveScheme(HrmEmployeeSocialSecurityDO socialSecurity, HrmEmployeeDO employee,
+                                                    LocalDate periodDate,
+                                                    Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache) {
+        if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
+            HrmSocialSecuritySchemeDO scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
+            if (scheme != null) {
+                return scheme;
+            }
+        }
+        Long deptId = employee.getDeptId();
+        if (!deptSchemeCache.containsKey(deptId)) {
+            deptSchemeCache.put(deptId, socialSecuritySchemeService.resolveSchemeByDeptId(deptId, periodDate));
+        }
+        return deptSchemeCache.get(deptId);
     }
 
     /**
@@ -531,6 +566,8 @@
 
         // 2. 閬嶅巻鍛樺伐瀹炴椂璁$畻钖叕
         List<HrmSalaryCalculationDO> resultList = new java.util.ArrayList<>();
+        // 閮ㄩ棬 -> 鏂规 鐨勮В鏋愮粨鏋滃湪鏈棰勮鍐呯紦瀛橈紝閬垮厤閫愬憳宸ラ噸澶嶆煡搴�
+        Map<Long, HrmSocialSecuritySchemeDO> deptSchemeCache = new HashMap<>();
         for (HrmEmployeeDO employee : employees) {
             Long employeeId = employee.getId();
 
@@ -543,11 +580,8 @@
 
             // 2.2 鑾峰彇鍛樺伐绀句繚鍏Н閲戞。妗堬紙鎸夊憳宸D鏌ヨ锛屽彇鏈�鏂扮敓鏁堢殑锛�
             HrmEmployeeSocialSecurityDO socialSecurity = employeeSocialSecurityMapper.selectLatestByUserId(employeeId);
-            // 2.3 濡傛灉鏈夌ぞ淇濇。妗堬紝鑾峰彇缁戝畾鐨勬柟妗�
-            HrmSocialSecuritySchemeDO scheme = null;
-            if (socialSecurity != null && socialSecurity.getSocialSecuritySchemeId() != null) {
-                scheme = socialSecuritySchemeMapper.selectById(socialSecurity.getSocialSecuritySchemeId());
-            }
+            // 2.3 鑾峰彇鏂规锛氬憳宸ユ。妗堟寚瀹� > 鎵�鍦ㄩ儴闂ㄧ粦瀹� > 鍏ㄥ眬榛樿
+            HrmSocialSecuritySchemeDO scheme = resolveScheme(socialSecurity, employee, effectiveDate, deptSchemeCache);
 
             // 2.4 鏋勫缓鏍哥畻璁板綍锛堝疄鏃惰绠楋紝涓嶅瓨鍌級
             HrmSalaryCalculationDO calculation = buildSalaryCalculation(period, employeeId, employee, employeeSalary, socialSecurity, scheme, effectiveDate);
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java
index 51a52e0..859e3b0 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeService.java
@@ -1,8 +1,12 @@
 package cn.iocoder.yudao.module.hrm.service.salary;
 
+import cn.iocoder.yudao.module.hrm.controller.admin.salary.vo.HrmSocialSecuritySchemeSaveReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
 
+import java.time.LocalDate;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
 
 /**
  * 绀句繚鍏Н閲戞柟妗� Service 鎺ュ彛
@@ -12,19 +16,19 @@
 public interface HrmSocialSecuritySchemeService {
 
     /**
-     * 鍒涘缓绀句繚鍏Н閲戞柟妗�
+     * 鍒涘缓绀句繚鍏Н閲戞柟妗堬紙鍚�傜敤閮ㄩ棬锛�
      *
-     * @param scheme 鏂规淇℃伅
+     * @param createReqVO 鏂规淇℃伅
      * @return 鏂规ID
      */
-    Long createScheme(HrmSocialSecuritySchemeDO scheme);
+    Long createScheme(HrmSocialSecuritySchemeSaveReqVO createReqVO);
 
     /**
-     * 鏇存柊绀句繚鍏Н閲戞柟妗�
+     * 鏇存柊绀句繚鍏Н閲戞柟妗堬紙鍚�傜敤閮ㄩ棬锛屾暣缁勬浛鎹級
      *
-     * @param scheme 鏂规淇℃伅
+     * @param updateReqVO 鏂规淇℃伅
      */
-    void updateScheme(HrmSocialSecuritySchemeDO scheme);
+    void updateScheme(HrmSocialSecuritySchemeSaveReqVO updateReqVO);
 
     /**
      * 鍒犻櫎绀句繚鍏Н閲戞柟妗�
@@ -49,4 +53,31 @@
      */
     List<HrmSocialSecuritySchemeDO> getSchemeList(Integer status);
 
-}
\ No newline at end of file
+    /**
+     * 鑾峰彇鏂规缁戝畾鐨勯�傜敤閮ㄩ棬ID鍒楄〃
+     *
+     * @param schemeId 鏂规ID
+     * @return 閮ㄩ棬ID鍒楄〃
+     */
+    List<Long> getSchemeDeptIds(Long schemeId);
+
+    /**
+     * 鎵归噺鑾峰彇鏂规缁戝畾鐨勯�傜敤閮ㄩ棬ID锛堟柟妗圛D -> 閮ㄩ棬ID鍒楄〃锛�
+     *
+     * @param schemeIds 鏂规ID闆嗗悎
+     * @return 鏂规ID -> 閮ㄩ棬ID鍒楄〃
+     */
+    Map<Long, List<Long>> getSchemeDeptIdMap(Collection<Long> schemeIds);
+
+    /**
+     * 鎸夐儴闂ㄨВ鏋愭牳绠楃敤鐨勭ぞ淇濆叕绉噾鏂规
+     *
+     * 浼樺厛绾э細璇ラ儴闂ㄧ粦瀹氱殑鏂规锛堢敓鏁堟棩鏈熶笉鏅氫簬鏍哥畻鏈熼棿锛屽彇鏈�鏂帮級> 鍏ㄥ眬榛樿鏂规锛堟湭缁戝畾浠讳綍閮ㄩ棬鐨勬柟妗堜腑鐢熸晥鏃ユ湡鏈�鏂拌�咃級
+     *
+     * @param deptId      鍛樺伐鎵�鍦ㄩ儴闂↖D锛堝彲涓虹┖锛�
+     * @param periodDate  鏍哥畻鏈熼棿鐨勬湀鍒濇棩鏈�
+     * @return 鏂规锛涙煡涓嶅埌杩斿洖 null锛岀敱璋冪敤鏂逛娇鐢ㄩ粯璁ゆ瘮渚嬪父閲�
+     */
+    HrmSocialSecuritySchemeDO resolveSchemeByDeptId(Long deptId, LocalDate periodDate);
+
+}
diff --git a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java
index 142653a..5be274f 100644
--- a/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java
+++ b/yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java
@@ -1,18 +1,36 @@
 package cn.iocoder.yudao.module.hrm.service.salary;
 
+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.hrm.controller.admin.salary.vo.HrmSocialSecuritySchemeSaveReqVO;
 import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDO;
+import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecuritySchemeDeptDO;
+import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeDeptMapper;
 import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecuritySchemeMapper;
+import cn.iocoder.yudao.module.system.api.dept.DeptApi;
+import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
 import jakarta.annotation.Resource;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 import org.springframework.validation.annotation.Validated;
 
+import java.time.LocalDate;
+import java.util.Collection;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import java.util.stream.Collectors;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SOCIAL_SECURITY_SCHEME_DEPT_EXISTS;
 import static cn.iocoder.yudao.module.hrm.enums.ErrorCodeConstants.SOCIAL_SECURITY_SCHEME_NOT_EXISTS;
 
 /**
  * 绀句繚鍏Н閲戞柟妗� Service 瀹炵幇绫�
+ *
+ * 鏂规鐨勭即绾虫瘮渚嬫寜銆屾柟妗� - 閫傜敤閮ㄩ棬銆嶅叧鑱旇〃鐢熸晥锛涗竴涓儴闂ㄥ彧鑳界粦瀹氫竴涓柟妗堬紝鏈粦瀹氫换浣曢儴闂ㄧ殑鏂规浣滀负鍏ㄥ眬榛樿鏂规銆�
  *
  * @author 瓒呯骇绠$悊鍛�
  */
@@ -20,24 +38,42 @@
 @Validated
 public class HrmSocialSecuritySchemeServiceImpl implements HrmSocialSecuritySchemeService {
 
+    /**
+     * 鍚敤鐘舵��
+     */
+    private static final Integer STATUS_ENABLE = 0;
+
     @Resource
     private HrmSocialSecuritySchemeMapper schemeMapper;
 
+    @Resource
+    private HrmSocialSecuritySchemeDeptMapper schemeDeptMapper;
+
+    @Resource
+    private DeptApi deptApi;
+
     @Override
-    public Long createScheme(HrmSocialSecuritySchemeDO scheme) {
+    @Transactional(rollbackFor = Exception.class)
+    public Long createScheme(HrmSocialSecuritySchemeSaveReqVO createReqVO) {
+        HrmSocialSecuritySchemeDO scheme = BeanUtils.toBean(createReqVO, HrmSocialSecuritySchemeDO.class);
         schemeMapper.insert(scheme);
+        saveSchemeDepts(scheme.getId(), createReqVO.getName(), createReqVO.getDeptIds());
         return scheme.getId();
     }
 
     @Override
-    public void updateScheme(HrmSocialSecuritySchemeDO scheme) {
-        validateSchemeExists(scheme.getId());
-        schemeMapper.updateById(scheme);
+    @Transactional(rollbackFor = Exception.class)
+    public void updateScheme(HrmSocialSecuritySchemeSaveReqVO updateReqVO) {
+        validateSchemeExists(updateReqVO.getId());
+        schemeMapper.updateById(BeanUtils.toBean(updateReqVO, HrmSocialSecuritySchemeDO.class));
+        saveSchemeDepts(updateReqVO.getId(), updateReqVO.getName(), updateReqVO.getDeptIds());
     }
 
     @Override
+    @Transactional(rollbackFor = Exception.class)
     public void deleteScheme(Long id) {
         validateSchemeExists(id);
+        schemeDeptMapper.deleteBySchemeId(id);
         schemeMapper.deleteById(id);
     }
 
@@ -51,10 +87,115 @@
         return schemeMapper.selectListByStatus(status);
     }
 
-    private void validateSchemeExists(Long id) {
-        if (schemeMapper.selectById(id) == null) {
-            throw exception(SOCIAL_SECURITY_SCHEME_NOT_EXISTS);
+    @Override
+    public List<Long> getSchemeDeptIds(Long schemeId) {
+        return schemeDeptMapper.selectListBySchemeId(schemeId).stream()
+                .map(HrmSocialSecuritySchemeDeptDO::getDeptId)
+                .toList();
+    }
+
+    @Override
+    public Map<Long, List<Long>> getSchemeDeptIdMap(Collection<Long> schemeIds) {
+        if (CollUtil.isEmpty(schemeIds)) {
+            return Map.of();
+        }
+        return schemeDeptMapper.selectListBySchemeIds(schemeIds).stream()
+                .collect(Collectors.groupingBy(HrmSocialSecuritySchemeDeptDO::getSchemeId,
+                        Collectors.mapping(HrmSocialSecuritySchemeDeptDO::getDeptId, Collectors.toList())));
+    }
+
+    @Override
+    public HrmSocialSecuritySchemeDO resolveSchemeByDeptId(Long deptId, LocalDate periodDate) {
+        // 1. 浼樺厛鍙栬閮ㄩ棬缁戝畾鐨勬柟妗�
+        if (deptId != null) {
+            List<Long> boundSchemeIds = schemeDeptMapper.selectListByDeptId(deptId).stream()
+                    .map(HrmSocialSecuritySchemeDeptDO::getSchemeId)
+                    .toList();
+            HrmSocialSecuritySchemeDO scheme = pickLatestEnabled(boundSchemeIds, periodDate);
+            if (scheme != null) {
+                return scheme;
+            }
+        }
+        // 2. 鍏滃簳鍙栧叏灞�榛樿鏂规锛氭湭缁戝畾浠讳綍閮ㄩ棬鐨勬柟妗堜腑鐢熸晥鏃ユ湡鏈�鏂拌��
+        Set<Long> boundSchemeIds = schemeDeptMapper.selectAllBoundSchemeIds();
+        List<Long> globalSchemeIds = schemeMapper.selectListByStatus(STATUS_ENABLE).stream()
+                .map(HrmSocialSecuritySchemeDO::getId)
+                .filter(id -> !boundSchemeIds.contains(id))
+                .toList();
+        return pickLatestEnabled(globalSchemeIds, periodDate);
+    }
+
+    /**
+     * 浠庡�欓�夋柟妗堜腑鍙栥�岀敓鏁堟棩鏈熶笉鏅氫簬鏍哥畻鏈熼棿銆嶇殑鏈�鏂颁竴鏉″惎鐢ㄦ柟妗�
+     *
+     * 鑻ュ叏閮ㄥ�欓�夋柟妗堥兘鏅氫簬鏍哥畻鏈熼棿锛堜緥濡傚垰寤哄ソ涓嬫湀鎵嶇敓鏁堢殑鏂规灏辨彁鍓嶆牳绠楋級锛屽垯閫�鍖栦负鍙栧叾涓敓鏁堟棩鏈熸渶鏂扮殑涓�鏉★紝
+     * 閬垮厤鏍哥畻閲戦闈欓粯钀藉埌浠g爜閲岀殑榛樿姣斾緥涓娿��
+     */
+    private HrmSocialSecuritySchemeDO pickLatestEnabled(Collection<Long> schemeIds, LocalDate periodDate) {
+        if (CollUtil.isEmpty(schemeIds)) {
+            return null;
+        }
+        List<HrmSocialSecuritySchemeDO> schemes = schemeMapper.selectList(
+                new LambdaQueryWrapperX<HrmSocialSecuritySchemeDO>()
+                        .in(HrmSocialSecuritySchemeDO::getId, schemeIds)
+                        .eq(HrmSocialSecuritySchemeDO::getStatus, STATUS_ENABLE)
+                        .orderByDesc(HrmSocialSecuritySchemeDO::getEffectiveDate)
+                        .orderByDesc(HrmSocialSecuritySchemeDO::getId));
+        if (CollUtil.isEmpty(schemes)) {
+            return null;
+        }
+        return schemes.stream()
+                .filter(scheme -> scheme.getEffectiveDate() == null
+                        || !scheme.getEffectiveDate().isAfter(periodDate))
+                .findFirst()
+                .orElse(schemes.get(0));
+    }
+
+    /**
+     * 鏁寸粍鏇挎崲鏂规鐨勯�傜敤閮ㄩ棬锛氬厛鏍¢獙锛屽啀鍒犳棫琛屻�佹彃鏂拌
+     */
+    private void saveSchemeDepts(Long schemeId, String schemeName, List<Long> deptIds) {
+        List<Long> distinctDeptIds = CollUtil.isEmpty(deptIds) ? List.of()
+                : deptIds.stream().filter(Objects::nonNull).distinct().toList();
+        if (CollUtil.isNotEmpty(distinctDeptIds)) {
+            // 閮ㄩ棬蹇呴』瀛樺湪
+            deptApi.validateDeptList(distinctDeptIds);
+            // 涓�涓儴闂ㄥ彧鑳界粦瀹氫竴涓柟妗�
+            validateDeptNotBoundByOtherScheme(schemeId, schemeName, distinctDeptIds);
+        }
+        schemeDeptMapper.deleteBySchemeId(schemeId);
+        distinctDeptIds.forEach(deptId -> schemeDeptMapper.insert(
+                HrmSocialSecuritySchemeDeptDO.builder().schemeId(schemeId).deptId(deptId).build()));
+    }
+
+    /**
+     * 鏍¢獙杩欎簺閮ㄩ棬娌℃湁琚埆鐨勬柟妗堝崰鐢�
+     */
+    private void validateDeptNotBoundByOtherScheme(Long schemeId, String schemeName, List<Long> deptIds) {
+        List<HrmSocialSecuritySchemeDeptDO> bindings = schemeDeptMapper.selectListByDeptIds(deptIds);
+        if (CollUtil.isEmpty(bindings)) {
+            return;
+        }
+        // 鍚嶇О瑙f瀽鍙斁鍦ㄥ紓甯稿垎鏀紝閬垮厤姝e父淇濆瓨澶氭煡琛�
+        for (HrmSocialSecuritySchemeDeptDO binding : bindings) {
+            if (Objects.equals(binding.getSchemeId(), schemeId)) {
+                continue;
+            }
+            HrmSocialSecuritySchemeDO otherScheme = schemeMapper.selectById(binding.getSchemeId());
+            DeptRespDTO dept = deptApi.getDept(binding.getDeptId());
+            throw exception(SOCIAL_SECURITY_SCHEME_DEPT_EXISTS,
+                    dept != null ? dept.getName() : "ID=" + binding.getDeptId(),
+                    otherScheme != null ? otherScheme.getName() : "ID=" + binding.getSchemeId(),
+                    schemeName);
         }
     }
 
-}
\ No newline at end of file
+    private HrmSocialSecuritySchemeDO validateSchemeExists(Long id) {
+        HrmSocialSecuritySchemeDO scheme = schemeMapper.selectById(id);
+        if (scheme == null) {
+            throw exception(SOCIAL_SECURITY_SCHEME_NOT_EXISTS);
+        }
+        return scheme;
+    }
+
+}

--
Gitblit v1.9.3