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): 社保公积金方案支持按部门绑定,修比例输入精度

---
 yudao-module-hrm/src/main/java/cn/iocoder/yudao/module/hrm/service/salary/HrmSocialSecuritySchemeServiceImpl.java |  157 +++++++++++++++++++++++++++++++++++++++++++++++++--
 1 files changed, 149 insertions(+), 8 deletions(-)

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