| | |
| | | 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 超级管理员 |
| | | */ |
| | |
| | | @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); |
| | | } |
| | | |
| | |
| | | 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; |
| | | } |
| | | |
| | | } |