gaoluyang
6 小时以前 d37c463e8dcdc72edf992f8c010b5c9037a3dceb
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);
    }
    /**
     * 从候选方案中取「生效日期不晚于核算期间」的最新一条启用方案
     *
     * 若全部候选方案都晚于核算期间(例如刚建好下月才生效的方案就提前核算),则退化为取其中生效日期最新的一条,
     * 避免核算金额静默落到代码里的默认比例上。
     */
    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;
        }
        // 名称解析只放在异常分支,避免正常保存多查表
        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);
        }
    }
}
    private HrmSocialSecuritySchemeDO validateSchemeExists(Long id) {
        HrmSocialSecuritySchemeDO scheme = schemeMapper.selectById(id);
        if (scheme == null) {
            throw exception(SOCIAL_SECURITY_SCHEME_NOT_EXISTS);
        }
        return scheme;
    }
}