第2-4-8章 规则引擎Drools实战(1)-个人所得税计算器
- 全套代码及资料全部完整提供,点此处下载
9.1 个人所得税计算器
本小节我们需要通过Drools规则引擎来根据规则计算个人所得税,最终数据效果如下:
9.1.1 名词解释
税前月收入:即税前工资,指交纳个人所得税之前的总工资
应纳税所得额:指按照税法规定确定纳税人在一定期间所获得的所有应税收入减除在该纳税期间依法允许减除的各种支出后的余额
税率:是对征税对象的征收比例或征收额度
速算扣除数:指为解决超额累进税率分级计算税额的复杂技术问题,而预先计算出的一个数据,可以简化计算过程
扣税额:是指实际缴纳的税额
税后工资:是指扣完税后实际到手的工资收入
9.1.2 计算规则
要实现个人所得税计算器,需要了解2019个税扣缴变化:
应纳税所得额=(月收入-五险一金-起征点-依法确定的其他扣除-专项附加扣除)*适用税率
9.1.2.1 新税制主要有哪些变化?
- 扣缴义务人在支付居民个人工资、薪金所得时,需按照“累计预扣法”核算预扣个人所得税。较之前按月的税率表变化成按年计税,适用个人所得税预扣率表
- 首次设立了子女教育、继续教育、大病医疗、住房贷款利息或住房租金、赡养老人六项专项附加扣除。(在税前工资扣除相应额度后核算个税)
9.1.2.2 资较高人员本次个税较少,可能到年底扣税增加?
〔例1〕某职员2015年入职,2019年每月应发工资均为10000元,每月减除费用5000元,“三险一金”等专项扣除为1500元,从1月起享受子女教育专项附加扣除1000元,没有减免收入及减免税额等情况,以前三个月为例,应当按照以下方法计算预扣预缴税额:
1月份:(10000-5000-1500-1000)×3% =75元;
2月份:(10000×2-5000×2-1500×2-1000×2)×3%-75 =75元;
3月份:(10000×3-5000×3-1500×3-1000×3)×3%-75-75 =75元;
进一步计算可知,该纳税人全年累计预扣预缴应纳税所得额为30000元,一直适用3%的税率,因此各月应预扣预缴的税款相同
〔例2〕某职员2015年入职,2019年每月应发工资均为30000元,每月减除费用5000元,“三险一金”等专项扣除为4500元,享受子女教育、赡养老人两项专项附加扣除共计2000元,没有减免收入及减免税额等情况,以前三个月为例,应当按照以下方法计算各月应预扣预缴税额:
1月份:(30000–5000-4500-2000)×3% = 555元;
2月份:(30000×2-5000×2-4500×2-2000×2)×10% -2520 -555 =625元;
3月份:(30000×3-5000×3-4500×3-2000×3)×10% -2520 -555-625 =1850元;
按照上述算法,每一个月应纳个税额:
项目 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月 每月税前工资 30000 30000 30000 30000 30000 30000 30000 30000 30000 30000 30000 30000 扣除各项减免额后应对税率 3% 10% 10% 10% 10% 10% 10% 20% 20% 20% 20% 20% 月应预扣预缴个税 555 625 1850 1850 1850 1850 1850 2250 3700 3700 3700 3700 上述计算结果表明,由于2月份累计预扣预缴应纳税所得额为37000元,已适用10%的税率,因此2月份和3月份应预扣预缴有所增高。8月份已适用于20%的税率。根据上述计算结果表明,虽每月工资薪金额和相关扣除额相同,年度内越往后,应纳税所得额越大适用税率越高,则预扣预缴税款可能会越大,会出现类似“12月取得工资10000元预扣预缴的个税税款,比1月份取得工资薪金30000元预扣预缴的个税税款多的”现象。
〔例3〕某职员2022年每月应发工资均为15000元,每月减除费用5000元,养老保险个人缴纳8%,为15000×8%=1200元,失业保险个人缴纳0.5%,为15000×0.5%=75元,医疗保险个人缴纳2%+3元,为15000×2%+3=303元,住房公积金个人缴纳比例12%,为15000×12%=1800元,享受住房租金专项附加扣除1500元,没有减免收入及减免税额等情况,以前三个月的计算为例,应当按照以下方法计算各月应预扣预缴税额:
应纳税所得额=月收入-五险一金-起征点-依法确定的其他扣除-专项附加扣除
1月应纳税所得额=15000–5000-1200-75-303-1800-1500=5122元
1月份:1月应纳税所得额 × 3% = 153.66元;
2月份:1月应纳税所得额 × 2 × 3% -153.66 =153.66元;
3月份:1月应纳税所得额 × 3 × 3% -153.66 - 153.66=153.66元;
按照上述算法,每一个月应纳个税额:
项目 1月 2月 3月 4月 5月 6月 7月 8月 9月 10月 11月 12月 每月税前工资 15000 15000 15000 15000 15000 15000 15000 15000 15000 15000 15000 15000 扣除各项减免额后应对税率 3% 3% 3% 3% 3% 3% 3% 10% 10% 10% 10% 10% 月应预扣预缴个税 153.66 153.66 153.66 153.66 153.66 153.66 153.66 501.98 512.2 512.2 512.2 512.2 上述计算结果表明,由于8月份累计预扣预缴应纳税所得额为40976元,已适用10%的税率,因此8月份应预扣预缴有所增高。
9.1.2.3 关于年度汇算清缴
一般来讲,有以下情形之一的,您可以选择在次年3月1日至6月30日内,自行向汇缴地主管税务机关办理汇算清缴申报时进行专项附加扣除,税款多退少补;(1)不愿意通过单位办理扣除,未将相关专项附加扣除信息报送给任职受雇单位的;
(2)在同一纳税年度涉及有两处工资薪金所得或涉及劳务报酬所得的;
(3)有大病医疗支出项目的;
(4)纳税年度内未享受或未足额享受专项附加扣除等情形。
9.1.2.4 个人所得税预扣率表(居民个人工资、薪金所得预扣预缴适用)
级数 全年累计预扣预缴应纳税所得额 税率(%) 速算扣除数 1 不超过36,000元的部分 3 0 2 超过36,000元至144,000元的部分 10 2520 3 超过144,000元至300,000元的部分 20 16920 4 超过300,000元至420,000元的部分 25 31920 5 超过420,000元至660,000元的部分 30 52920 6 超过660,000元至960,000元的部分 35 85920 7 超过960,000元的部分 45 181920 9.1.2.5 五险一金缴费比例
五险一金缴费比例:养老保险单位21%,个人8%;医疗保险单位9%,个人2%+3元;失业保险单位0.5%,个人0.5%;工伤保险和生育保险由单位缴纳,劳动者不用缴;住房公积金根据企业的实际情况,选择住房公积金缴费比例,下限为5%,最高不超12%。
9.1.3 实现步骤
本实战案例我们基于Spring Boot整合Drools的方式来实现。
目录结构
9.1.3.1 创建maven工程calculation并配置pom.xml文件
<?xml version="1.0" encoding="UTF-8"?>
4.0.0 org.springframework.boot spring-boot-starters 2.0.6.RELEASE cn.itcast calculation 1.0-SNAPSHOT org.springframework.boot spring-boot-starter-web org.springframework.boot spring-boot-starter-aop org.springframework.boot spring-boot-starter-test commons-lang commons-lang 2.6 org.drools drools-core 7.6.0.Final org.drools drools-compiler 7.6.0.Final org.drools drools-templates 7.6.0.Final org.kie kie-api 7.6.0.Final org.kie kie-spring org.springframework spring-tx org.springframework spring-beans org.springframework spring-core org.springframework spring-context 7.6.0.Final ${project.artifactId} src/main/java **/*.xml false src/main/resources **/*.* false org.apache.maven.plugins maven-compiler-plugin 2.3.2 1.8 9.1.3.2 创建/resources/application.yml文件
server: port: 8080 spring: application: name: calculation
9.1.3.3 编写配置类DroolsConfig
package com.guohaowei.drools.config; import cn.hutool.core.date.DatePattern; import org.kie.api.KieBase; import org.kie.api.KieServices; import org.kie.api.builder.KieBuilder; import org.kie.api.builder.KieFileSystem; import org.kie.api.builder.KieRepository; import org.kie.api.runtime.KieContainer; import org.kie.internal.io.ResourceFactory; import org.kie.spring.KModuleBeanFactoryPostProcessor; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import java.io.IOException; /** * 规则引擎配置类 */ @Configuration public class DroolsConfig { //指定规则文件存放的目录 private static final String RULES_PATH = "rules/"; private final KieServices kieServices = KieServices.Factory.get(); @Bean @ConditionalOnMissingBean public KieFileSystem kieFileSystem() throws IOException { System.setProperty("drools.dateformat", DatePattern.NORM_DATE_PATTERN); KieFileSystem kieFileSystem = kieServices.newKieFileSystem(); ResourcePatternResolver resourcePatternResolver = new PathMatchingResourcePatternResolver(); Resource[] files = resourcePatternResolver.getResources("classpath*:" + RULES_PATH + "*.*"); String path; for (Resource file : files) { path = RULES_PATH + file.getFilename(); kieFileSystem.write(ResourceFactory.newClassPathResource(path, "UTF-8")); } return kieFileSystem; } @Bean @ConditionalOnMissingBean public KieContainer kieContainer() throws IOException { KieRepository kieRepository = kieServices.getRepository(); kieRepository.addKieModule(kieRepository::getDefaultReleaseId); KieBuilder kieBuilder = kieServices.newKieBuilder(kieFileSystem()); kieBuilder.buildAll(); return kieServices.newKieContainer(kieRepository.getDefaultReleaseId()); } @Bean @ConditionalOnMissingBean public KieBase kieBase() throws IOException { return kieContainer().getKieBase(); } @Bean @ConditionalOnMissingBean public KModuleBeanFactoryPostProcessor kiePostProcessor() { return new KModuleBeanFactoryPostProcessor(); } }
9.1.3.4 编写各种实体类
- 编写实体类CalculationDto
package com.guohaowei.drools.domain.dto; import com.guohaowei.drools.domain.vo.CalculationVo; import lombok.Data; import java.math.BigDecimal; import java.util.List; /** * @author guohaowei */ @Data public class CalculationDto { /** * 12个月的工资缴税额等信息 */ private List
calculationVoList; //--------------------------------重复字段用于计算start-------------------------------------------------------- /** * 税前工资 */ private BigDecimal wageBeforeTax; /** * 预扣预缴应纳税所得额 */ private BigDecimal taxableIncome; /** * 累计预扣预缴应纳税所得额 */ private BigDecimal taxableIncomeCount; /** * 月税后工资 */ private BigDecimal wageDeductedTax; //--------------------------------重复字段用于计算end---------------------------------------------------------- /** * 个人所得税起征点 */ private BigDecimal threshold; /** * 养老保险个人缴纳比例,默认8% */ private BigDecimal oldAgeInsuranceRatio; /** * 失业保险个人缴纳比例,默认0.5% */ private BigDecimal unemploymentInsuranceRatio; /** * 医疗保险个人缴纳比例,默认2% + 3元 */ private BigDecimal medicalInsuranceRatio = BigDecimal.valueOf(0.02); /** * 医疗保险个人缴纳比例,默认2% + 3元 */ private BigDecimal medicalInsuranceAdd = BigDecimal.valueOf(3); /** * 住房公积金个人缴纳比例,默认12% */ private BigDecimal housingFundRatio; /** * 住房租金专项附加扣除,默认1500元 * 其他专项附加扣除同理,此处省略 */ private BigDecimal specialAdditionalDeductionRent; } - 编写实体类CalculationVo
package com.guohaowei.drools.domain.vo; import lombok.Builder; import lombok.Data; import java.math.BigDecimal; /** * @author guohaowei */ @Data @Builder public class CalculationVo { /** * 月税前工资 */ private BigDecimal wageBeforeTax; /** * 月预扣预缴应纳税所得额 */ private BigDecimal taxableIncome; /** * 月税率 */ private BigDecimal taxRate; /** * 月速算扣除数 */ private BigDecimal quickDeduction; /** * 月扣税额 */ private BigDecimal taxRebate; /** * 月税后工资 */ private BigDecimal wageDeductedTax; /** * 月份 */ private Integer month; }
- 编写枚举类WithholdingTaxRateEnum
package com.guohaowei.drools.enums; import lombok.AllArgsConstructor; import lombok.Getter; import java.math.BigDecimal; /** * @Author: 郭浩伟 qq:912161367 * @Date: 2022/11/17 0017 7:18 * @Description: 个人所得税预扣率表(居民个人工资、薪金所得预扣预缴适用) */ @AllArgsConstructor @Getter public enum WithholdingTaxRateEnum { /** * 级数 */ one(36_000, "0.03", 0), two(144_000, "0.10", 2520), three(300_000, "0.20", 16920), four(420_000, "0.25", 31920), five(660_000, "0.30", 52920), six(960_000, "0.35", 85920), seven(960_000, "0.45", 181920), ; /** * 全年累计预扣预缴应纳税所得额 */ private Integer taxableIncome; /** * 税率,如0.03 */ private String taxRate; /** * 速算扣除数 */ private Integer quickDeduction; /** * 根据【应纳税所得额】得到含对应的【税率】和【速算扣除数】的枚举值 * * @param actualTaxableIncome * @return */ public static WithholdingTaxRateEnum getInfoByIncome(BigDecimal actualTaxableIncome) { if(null == actualTaxableIncome){ return null; } for (WithholdingTaxRateEnum value : WithholdingTaxRateEnum.values()) { if(actualTaxableIncome.compareTo(BigDecimal.valueOf(value.getTaxableIncome())) <= 0){ return value; } } return seven; } }
- 编写实体类CalculationReq
package com.guohaowei.drools.domain.req; import lombok.Data; import java.math.BigDecimal; /** * @Author: 郭浩伟 qq:912161367 * @Date: 2022/11/16 0016 23:31 * @Description: */ @Data public class CalculationReq { /** * 税前工资 */ private BigDecimal wageBeforeTax; /** * 个人所得税起征点,默认5000元 */ private BigDecimal threshold = BigDecimal.valueOf(5000); /** * 养老保险个人缴纳比例,默认8% */ private BigDecimal oldAgeInsuranceRatio = BigDecimal.valueOf(0.08); /** * 失业保险个人缴纳比例,默认0.5% */ private BigDecimal unemploymentInsuranceRatio = BigDecimal.valueOf(0.005); /** * 医疗保险个人缴纳比例,默认2% + 3元 */ private BigDecimal medicalInsuranceRatio = BigDecimal.valueOf(0.02); /** * 医疗保险个人缴纳比例,默认2% + 3元 */ private BigDecimal medicalInsuranceAdd = BigDecimal.valueOf(3); /** * 住房公积金个人缴纳比例,默认12% */ private BigDecimal housingFundRatio = BigDecimal.valueOf(0.12); /** * 住房租金专项附加扣除,默认1500元 * 其他专项附加扣除同理,此处省略 */ private BigDecimal specialAdditionalDeductionRent = BigDecimal.valueOf(1500); }
9.1.3.5 在resources/rules下创建规则文件calculation.drl文件
//当前规则文件用于计算个人所得税 package calculation import java.math.BigDecimal import com.guohaowei.drools.domain.dto.CalculationDto import com.guohaowei.drools.enums.WithholdingTaxRateEnum import java.util.ArrayList import com.guohaowei.drools.domain.vo.CalculationVo import java.util.List global com.guohaowei.drools.domain.dto.CalculationDto calculationGlobalDto /** 当前规则文件中的规则主要分为三类 1、计算应纳税所得额有1个规则 2、设置税率、速算扣除数有7个规则 3、计算税后工资有1个规则 **/ //计算应纳税所得额 rule "计算应纳税所得额-减去个税起征点" salience 100 date-effective "2019-01-01" no-loop true when then //税前工资 BigDecimal wageBeforeTax = calculationGlobalDto.getWageBeforeTax(); BigDecimal taxableIncome = wageBeforeTax.subtract(calculationGlobalDto.getThreshold()); calculationGlobalDto.setWageBeforeTax(wageBeforeTax); calculationGlobalDto.setWageDeductedTax(wageBeforeTax); calculationGlobalDto.setTaxableIncome(BigDecimal.ZERO); if(taxableIncome.compareTo(BigDecimal.valueOf(0)) > 0){ calculationGlobalDto.setTaxableIncome(taxableIncome); } end rule "计算应纳税所得额-减去养老保险个人缴纳金额" salience 99 date-effective "2019-01-01" no-loop true when then //养老保险个人缴纳额 BigDecimal oldAgeInsurance = calculationGlobalDto.getWageBeforeTax().multiply(calculationGlobalDto.getOldAgeInsuranceRatio()); //预扣预缴应纳税所得额 BigDecimal taxableIncome = calculationGlobalDto.getTaxableIncome(); //应纳税所得额如果小于0即不需要缴税也就不需要计算了 if(taxableIncome.compareTo(BigDecimal.valueOf(0)) > 0){ taxableIncome = taxableIncome.subtract(oldAgeInsurance); calculationGlobalDto.setTaxableIncome(taxableIncome); } //税后工资 calculationGlobalDto.setWageDeductedTax(calculationGlobalDto.getWageBeforeTax().subtract(oldAgeInsurance)); end rule "计算应纳税所得额-减去失业保险个人缴纳金额" salience 98 date-effective "2019-01-01" no-loop true when then //失业保险个人缴纳额 BigDecimal unemploymentInsurance = calculationGlobalDto.getWageBeforeTax().multiply(calculationGlobalDto.getUnemploymentInsuranceRatio()); //应纳税所得额如果小于0即不需要缴税也就不需要计算了 if(calculationGlobalDto.getTaxableIncome().compareTo(BigDecimal.valueOf(0)) > 0){ BigDecimal taxableIncome = calculationGlobalDto.getTaxableIncome().subtract(unemploymentInsurance); calculationGlobalDto.setTaxableIncome(taxableIncome); } //税后工资 calculationGlobalDto.setWageDeductedTax(calculationGlobalDto.getWageDeductedTax().subtract(unemploymentInsurance)); end rule "计算应纳税所得额-减去医疗保险个人缴纳金额" salience 97 date-effective "2019-01-01" no-loop true when then //医疗保险个人缴纳额 BigDecimal medicalInsurance = calculationGlobalDto.getWageBeforeTax().multiply(calculationGlobalDto.getMedicalInsuranceRatio()).add(calculationGlobalDto.getMedicalInsuranceAdd()); //应纳税所得额如果小于0即不需要缴税也就不需要计算了 if(calculationGlobalDto.getTaxableIncome().compareTo(BigDecimal.valueOf(0)) > 0){ BigDecimal taxableIncome = calculationGlobalDto.getTaxableIncome().subtract(medicalInsurance); calculationGlobalDto.setTaxableIncome(taxableIncome); } //税后工资 calculationGlobalDto.setWageDeductedTax(calculationGlobalDto.getWageDeductedTax().subtract(medicalInsurance)); end rule "计算应纳税所得额-减去住房公积金个人缴纳金额" salience 96 date-effective "2019-01-01" no-loop true when then //住房公积金个人缴纳额 BigDecimal housingFund = calculationGlobalDto.getWageBeforeTax().multiply(calculationGlobalDto.getHousingFundRatio()); //应纳税所得额如果小于0即不需要缴税也就不需要计算了 if(calculationGlobalDto.getTaxableIncome().compareTo(BigDecimal.valueOf(0)) > 0){ BigDecimal taxableIncome = calculationGlobalDto.getTaxableIncome().subtract(housingFund); calculationGlobalDto.setTaxableIncome(taxableIncome); } //税后工资 calculationGlobalDto.setWageDeductedTax(calculationGlobalDto.getWageDeductedTax().subtract(housingFund)); end rule "计算应纳税所得额-减去住房租金专项附加扣除金额" salience 95 date-effective "2019-01-01" no-loop true when then //应纳税所得额如果小于0即不需要缴税也就不需要计算了 if(calculationGlobalDto.getTaxableIncome().compareTo(BigDecimal.valueOf(0)) > 0){ BigDecimal taxableIncome = calculationGlobalDto.getTaxableIncome().subtract(calculationGlobalDto.getSpecialAdditionalDeductionRent()); calculationGlobalDto.setTaxableIncome(taxableIncome); } end rule "计算出最终的统计结果" salience 94 date-effective "2019-01-01" no-loop true when then List
calculationVoList = new ArrayList<>(); BigDecimal taxRebateCount = BigDecimal.ZERO; //当月扣税额=应纳税所得额x月份数x税率-速算扣除数-累计扣税额 for(int i = 1; i <= 12 ; i++) { //累计应纳税所得额 BigDecimal taxableIncomeCount = calculationGlobalDto.getTaxableIncome().multiply(BigDecimal.valueOf(i)); WithholdingTaxRateEnum withholdingTaxRateEnum = WithholdingTaxRateEnum.getInfoByIncome(taxableIncomeCount); //月税率 BigDecimal taxRate = new BigDecimal(withholdingTaxRateEnum.getTaxRate()); //月速算扣除数 BigDecimal quickDeduction = BigDecimal.valueOf(withholdingTaxRateEnum.getQuickDeduction()); //月扣税额 BigDecimal taxRebate =taxableIncomeCount.multiply(taxRate).subtract(quickDeduction).subtract(taxRebateCount); //累计扣税额 taxRebateCount = taxRebateCount.add(taxRebate); //组装vo CalculationVo calculationVo = CalculationVo.builder().wageBeforeTax(calculationGlobalDto.getWageBeforeTax()).taxableIncome(calculationGlobalDto.getTaxableIncome()). taxRate(taxRate).quickDeduction(quickDeduction).taxRebate(taxRebate).wageDeductedTax(calculationGlobalDto.getWageDeductedTax().subtract(taxRebate)).month(i).build(); calculationVoList.add(calculationVo); } calculationGlobalDto.setCalculationVoList(calculationVoList); end 9.1.3.6 创建RuleServiceImpl
package com.guohaowei.drools.service.impl; import cn.hutool.core.bean.BeanUtil; import com.guohaowei.drools.domain.dto.CalculationDto; import com.guohaowei.drools.domain.req.CalculationReq; import com.guohaowei.drools.domain.vo.CalculationVo; import com.guohaowei.drools.service.RuleService; import org.kie.api.KieBase; import org.kie.api.runtime.KieSession; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import java.util.List; /** * @Author: 郭浩伟 qq:912161367 * @Date: 2022/11/18 0018 19:41 * @Description: */ @Service public class RuleServiceImpl implements RuleService { @Autowired private KieBase kieBase; /** * 调用Drools规则引擎实现个人所得税计算 * * @param req * @return */ @Override public List
calculate(CalculationReq req) { KieSession session = kieBase.newKieSession(); CalculationDto calculationDto = BeanUtil.copyProperties(req, CalculationDto.class); //初始化税后工资=税前工资 calculationDto.setWageDeductedTax(calculationDto.getWageBeforeTax()); //设置全局变量,名称和类型必须和规则文件中定义的全局变量名称对应 session.setGlobal("calculationGlobalDto",calculationDto); session.fireAllRules(); session.dispose(); return calculationDto.getCalculationVoList(); } } 9.1.3.7 创建RuleController
package com.guohaowei.drools.controller; import com.guohaowei.drools.domain.req.CalculationReq; import com.guohaowei.drools.domain.vo.CalculationVo; import com.guohaowei.drools.service.impl.RuleServiceImpl; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import java.util.List; @RestController @RequestMapping("/rule") public class RuleController { @Autowired private RuleServiceImpl ruleService; @RequestMapping("/calculate") public List
calculate(@RequestBody CalculationReq req){ return ruleService.calculate(req); } } 9.1.3.8 创建启动类DroolsApplication
package com.guohaowei.drools; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DroolsApplication { public static void main(String[] args) { SpringApplication.run(DroolsApplication.class); } }
全套代码及资料全部完整提供,点此处下载