liyong
9 天以前 88e384da863bb2f7324cb1e1474885df3b7cce91
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
package cn.iocoder.yudao.module.hrm.service.salary;
 
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmSocialSecurityConfigDO;
import cn.iocoder.yudao.module.hrm.dal.dataobject.salary.HrmTaxRateConfigDO;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmSocialSecurityConfigMapper;
import cn.iocoder.yudao.module.hrm.dal.mysql.salary.HrmTaxRateConfigMapper;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated;
 
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;
 
/**
 * 薪酬扣除计算 Service 实现类
 *
 * @author 芋道源码
 */
@Service
@Validated
public class HrmDeductionServiceImpl implements HrmDeductionService {
 
    /**
     * 个税起征点(每月5000元)
     */
    private static final BigDecimal TAX_THRESHOLD = new BigDecimal("5000");
 
    /**
     * 默认缴纳比例(当员工档案中没有配置时使用)
     */
    private static final BigDecimal DEFAULT_PENSION_PERSONAL_RATIO = new BigDecimal("0.08");
    private static final BigDecimal DEFAULT_MEDICAL_PERSONAL_RATIO = new BigDecimal("0.02");
    private static final BigDecimal DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO = new BigDecimal("0.005");
    private static final BigDecimal DEFAULT_HOUSING_FUND_PERSONAL_RATIO = new BigDecimal("0.12");
 
    @Resource
    private HrmSocialSecurityConfigMapper socialSecurityConfigMapper;
    @Resource
    private HrmTaxRateConfigMapper taxRateConfigMapper;
 
    @Override
    public BigDecimal calculateSocialSecurityDeduction(BigDecimal socialSecurityBase, LocalDate effectiveDate) {
        // 此方法保留作为备用,但不推荐使用
        // 推荐使用 calculateSocialSecurityDeductionByConfig 方法
        if (socialSecurityBase == null || socialSecurityBase.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        // 使用默认比例计算
        BigDecimal pension = socialSecurityBase.multiply(DEFAULT_PENSION_PERSONAL_RATIO);
        BigDecimal medical = socialSecurityBase.multiply(DEFAULT_MEDICAL_PERSONAL_RATIO);
        BigDecimal unemployment = socialSecurityBase.multiply(DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO);
        return pension.add(medical).add(unemployment).setScale(2, RoundingMode.HALF_UP);
    }
 
    @Override
    public BigDecimal calculateHousingFundDeduction(BigDecimal housingFundBase, LocalDate effectiveDate) {
        // 此方法保留作为备用,但不推荐使用
        if (housingFundBase == null || housingFundBase.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
        // 使用默认比例计算
        return housingFundBase.multiply(DEFAULT_HOUSING_FUND_PERSONAL_RATIO).setScale(2, RoundingMode.HALF_UP);
    }
 
    /**
     * 根据员工社保档案配置计算社保扣除(个人部分)
     *
     * @param socialSecurityBase 社保基数
     * @param pensionPersonalRatio 养老保险个人缴纳比例
     * @param medicalPersonalRatio 医疗保险个人缴纳比例
     * @param unemploymentPersonalRatio 失业保险个人缴纳比例
     * @return 社保扣除金额
     */
    public BigDecimal calculateSocialSecurityDeductionByConfig(
            BigDecimal socialSecurityBase,
            BigDecimal pensionPersonalRatio,
            BigDecimal medicalPersonalRatio,
            BigDecimal unemploymentPersonalRatio) {
        if (socialSecurityBase == null || socialSecurityBase.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
 
        // 使用传入的比例,如果为空则使用默认值
        BigDecimal pensionRatio = pensionPersonalRatio != null ? pensionPersonalRatio : DEFAULT_PENSION_PERSONAL_RATIO;
        BigDecimal medicalRatio = medicalPersonalRatio != null ? medicalPersonalRatio : DEFAULT_MEDICAL_PERSONAL_RATIO;
        BigDecimal unemploymentRatio = unemploymentPersonalRatio != null ? unemploymentPersonalRatio : DEFAULT_UNEMPLOYMENT_PERSONAL_RATIO;
 
        // 计算个人缴纳部分:养老 + 医疗 + 失业
        BigDecimal pension = socialSecurityBase.multiply(pensionRatio);
        BigDecimal medical = socialSecurityBase.multiply(medicalRatio);
        BigDecimal unemployment = socialSecurityBase.multiply(unemploymentRatio);
 
        return pension.add(medical).add(unemployment).setScale(2, RoundingMode.HALF_UP);
    }
 
    /**
     * 根据员工社保档案配置计算公积金扣除(个人部分)
     *
     * @param housingFundBase 公积金基数
     * @param housingFundPersonalRatio 公积金个人缴纳比例
     * @return 公积金扣除金额
     */
    public BigDecimal calculateHousingFundDeductionByConfig(
            BigDecimal housingFundBase,
            BigDecimal housingFundPersonalRatio) {
        if (housingFundBase == null || housingFundBase.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
 
        // 使用传入的比例,如果为空则使用默认值
        BigDecimal ratio = housingFundPersonalRatio != null ? housingFundPersonalRatio : DEFAULT_HOUSING_FUND_PERSONAL_RATIO;
 
        return housingFundBase.multiply(ratio).setScale(2, RoundingMode.HALF_UP);
    }
 
    @Override
    public BigDecimal calculateTaxDeduction(BigDecimal taxableIncome, LocalDate effectiveDate) {
        if (taxableIncome == null || taxableIncome.compareTo(BigDecimal.ZERO) <= 0) {
            return BigDecimal.ZERO;
        }
 
        List<HrmTaxRateConfigDO> taxRates = taxRateConfigMapper.selectListByEffectiveDate(effectiveDate);
        if (taxRates == null || taxRates.isEmpty()) {
            return BigDecimal.ZERO;
        }
 
        // 查找对应的税率档位
        for (HrmTaxRateConfigDO taxRate : taxRates) {
            BigDecimal minAmount = taxRate.getMinAmount();
            BigDecimal maxAmount = taxRate.getMaxAmount();
 
            // 判断是否在该档位范围内(maxAmount为0表示无上限)
            boolean inRange = taxableIncome.compareTo(minAmount) >= 0
                    && (maxAmount.compareTo(BigDecimal.ZERO) == 0 || taxableIncome.compareTo(maxAmount) < 0);
 
            if (inRange) {
                // 计算个税 = 应纳税所得额 * 税率 - 速算扣除数
                return taxableIncome.multiply(taxRate.getTaxRate())
                        .subtract(taxRate.getQuickDeduction())
                        .setScale(2, RoundingMode.HALF_UP);
            }
        }
 
        return BigDecimal.ZERO;
    }
 
    @Override
    public HrmSocialSecurityConfigDO getLatestSocialSecurityConfig(LocalDate effectiveDate) {
        return socialSecurityConfigMapper.selectLatestByEffectiveDate(effectiveDate);
    }
 
}