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 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); } }