gaoluyang
2 小时以前 d37c463e8dcdc72edf992f8c010b5c9037a3dceb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
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 超级管理员
 */
@Service
@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
    @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
    @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);
    }
 
    @Override
    public HrmSocialSecuritySchemeDO getScheme(Long id) {
        return schemeMapper.selectById(id);
    }
 
    @Override
    public List<HrmSocialSecuritySchemeDO> getSchemeList(Integer status) {
        return schemeMapper.selectListByStatus(status);
    }
 
    @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;
    }
 
}