Skip to content

Case 23: SME tax relief eligibility

This case corresponds to USE-CASES scenario 23, “SME tax relief / job-retention subsidy eligibility (requirement 21)”. It belongs to the fiscal and tax compliance domain, targeting the enterprise-benefit policy fulfillment scenarios of tax and human-resources departments. It automatically determines small and micro enterprise eligibility, computes the segmented enterprise income tax preference, applies the VAT levy rate for small-scale taxpayers, and determines the fixed job-retention rebate.

Small and micro enterprises apply for tax relief and job-retention subsidies through a unified enterprise-benefit window. Declared elements include: taxpayer type (general taxpayer GENERAL / small-scale taxpayer SMALL_SCALE), number of employees (including dispatched labor), total assets, annual taxable income, whether the business belongs to a restricted or prohibited industry, plus the elements used for job-retention review: consecutive months of social insurance contributions, last year’s layoff rate, whether there are social insurance arrears or overdue taxes, and the unemployment insurance premiums actually paid last year.

Small and micro enterprise eligibility requires all three conditions to be met simultaneously: no more than 300 employees, total assets no more than 50 million yuan, and annual taxable income no more than 3 million yuan (all three boundary values are inclusive). Qualified enterprises have their enterprise income tax assessed at a segmented effective tax burden: 5% on the portion of annual taxable income up to 1 million yuan, and 10% on the portion above 1 million yuan (i.e., 50,000 yuan + 10% of the excess); unqualified enterprises pay at the statutory 25% rate. The system also computes the statutory tax at the 25% rate, and the difference from the assessed tax is the tax relief amount.

Turnover tax and the job-retention rebate are reviewed in parallel: small-scale taxpayers pay VAT at a reduced 1% levy rate, while general taxpayers do not qualify for this preference (levy rate recorded as 0). The job-retention subsidy requires three conditions simultaneously: at least 12 consecutive months of social insurance contributions, last year’s layoff rate no higher than 5%, and no social insurance arrears. When eligible, the subsidy is assessed at 50% of the unemployment insurance premiums actually paid. For enterprises with overdue tax records, even if the three conditions are met, the job-retention rebate is suspended and must be applied for separately after clearance.

Restricted or prohibited industries are a hard exclusion: enterprises engaged in them are always DENIED, pay income tax at the statutory rate, have the VAT preference and job-retention rebate zeroed out, and receive the exclusion basis explicitly in the reasons. The final decision carries the SME eligibility flag, statutory tax, assessed tax, tax relief amount, levy rate, job-retention eligibility and subsidy amount, plus item-by-item policy bases in reasons, supporting the generation of enterprise-benefit fulfillment notices and tax filing records.

  • Tax preference rules change frequently with policy documents: the segmented effective tax burden for small and micro enterprises, the 1% levy rate, and the 50% job-retention rebate ratio may all be revised annually; once hard-coded into collection systems, every change must race against a release window.
  • Segmented tax computation is error-prone: 5% below 1 million and 10% on the 1-3 million portion is a “progressive excess segment”, not a full-amount bracket; writing it as a nested-if lookup table often miscalculates the entire amount at 10%.
  • Eligibility conditions and industry exclusion priority must be compliant: even if all three scale criteria are met, a restricted industry must not enjoy the preferences; the exclusion check must short-circuit before tax and subsidy computation.
  • Many preference types, and conclusions must be explainable item by item: income tax relief, the VAT levy rate, and the job-retention rebate are independent of each other; when an enterprise fails the SME conditions, it must be told item by item whether headcount, assets, or income exceeded the limits.
  • The impact of overdue taxes and arrears on subsidies is easy to miss: when layoff rate and social insurance conditions are met but overdue taxes exist, the subsidy is not rejected outright but “suspended”; the different policy wording must be distinguished.
  • Amount formatting must be precise: taxes and subsidies keep two decimal places and must not use scientific notation; concatenating a double directly can produce non-standard document wording such as “6000.0”.

This case follows the six-piece skeleton: input model + decision result model + rule interface + XML rule script + Service orchestration + 6 groups of tests based on real business scenarios.

Layer Responsibility Files
Domain model Tax relief and job-retention subsidy application elements and assessment result (eligibility / three amounts / policy reasons) TaxReliefApplication.java, TaxReliefDecision.java
Rule set and XML Segmented enterprise income tax, small-scale levy rate, job-retention eligibility conditions, 50% fixed rebate TaxReliefRules.java, tax-relief.xml
Orchestration service Three SME conditions and industry exclusion, overdue-tax suspension, statutory tax comparison, amount precision and reason assembly TaxReliefService.java
Tests 6 enterprise scenarios (low-bracket rate + job retention / cross-bracket rate / all three limits exceeded / boundary values / restricted industry / layoffs and overdue tax) TaxReliefServiceTest.java
Rule method Rule Score / output
incomeTaxWan SME-qualified: income ≤1M yuan portion x5%, excess = 5.0 + (income - 100) x10%; not qualified: x25% Tax in wan yuan
vatLevyRate SMALL_SCALE returns 0.01, otherwise 0.0 (preference not applicable) Levy rate
stabilizationEligible Social insurance >=12 months, layoff rate ≤5%, and no social insurance arrears all hold boolean
subsidyAmountWan When eligible, unemployment insurance premiums paid x50%, otherwise 0 Subsidy in wan yuan

Place the following files into the test directory of a JQuick-Java project following the source package structure: the two models go into src/test/java/com/github/paohaijiao/demo/model/, the rule interface and test class into src/test/java/com/github/paohaijiao/demo/, the Service into src/test/java/com/github/paohaijiao/demo/service/, and the XML script into src/test/resources/demo/, keeping the original package names (com.github.paohaijiao.demo / .model / .service). Then run mvn test -Dtest=TaxReliefServiceTest to execute all 6 test cases.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 小微企业税费减免与稳岗补贴申请要素
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class TaxReliefApplication {
/**
* 企业编号
*/
private String enterpriseId;
/**
* 企业名称
*/
private String enterpriseName;
/**
* 纳税人类型:GENERAL-一般纳税人 / SMALL_SCALE-小规模纳税人
*/
private String taxpayerType;
/**
* 从业人数(含劳务派遣)
*/
private int employeeCount;
/**
* 资产总额(万元)
*/
private double totalAssetsWan;
/**
* 年应纳税所得额(万元)
*/
private double annualTaxableIncomeWan;
/**
* 是否属于限制或禁止行业
*/
private boolean restrictedIndustry;
/**
* 社保连续缴纳月数(稳岗补贴审核用)
*/
private int socialInsuranceMonths;
/**
* 上年度裁员率(整数百分比)
*/
private int layoffRate;
/**
* 是否存在社保欠费
*/
private boolean socialArrears;
/**
* 是否存在欠税
*/
private boolean overdueTax;
/**
* 上年度实际缴纳失业保险费(万元)
*/
private double paidUnemploymentInsuranceWan;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 税费减免与稳岗补贴核定结果
*/
@Data
public class TaxReliefDecision {
/**
* 企业编号
*/
private String enterpriseId;
/**
* 办理结论:APPROVED-可享受优惠 / DENIED-不予享受
*/
private String result;
/**
* 是否具备小型微利企业资格
*/
private boolean smallQualified;
/**
* 法定应纳企业所得税(25% 口径,万元)
*/
private double statutoryTaxWan;
/**
* 核定应纳企业所得税(万元)
*/
private double incomeTaxWan;
/**
* 企业所得税减免额(万元)
*/
private double taxSavingWan;
/**
* 增值税征收率(小规模优惠为 0.01,不适用为 0)
*/
private double vatLevyRate;
/**
* 是否具备稳岗补贴资格
*/
private boolean stabilizationEligible;
/**
* 核定稳岗补贴金额(万元)
*/
private double stabilizationSubsidyWan;
/**
* 资格判定与政策依据
*/
private List<String> reasons = new ArrayList<String>();
public void addReason(String reason) {
this.reasons.add(reason);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 23:小微企业税费减免与稳岗补贴规则集
* <p>
* 小型微利企业所得税分段实际税负:100 万以下部分 5%、100-300 万部分 10%;
* 小规模纳税人增值税征收率 1%;稳岗补贴为实缴失业保险费的 50%。
* 规则脚本见 {@code src/test/resources/demo/tax-relief.xml}。
*/
public interface TaxReliefRules {
/** 核定企业所得税(万元):符合小微条件分段优惠,否则按 25% 法定税率 */
double incomeTaxWan(@Param("incomeWan") double incomeWan,
@Param("smallQualified") boolean smallQualified);
/** 增值税征收率:小规模纳税人减按 1%,一般纳税人不适用该优惠 */
double vatLevyRate(@Param("taxpayerType") String taxpayerType);
/** 稳岗补贴资格:社保满 12 月、裁员率不超过 5%、无社保欠费 */
boolean stabilizationEligible(@Param("ssMonths") int ssMonths,
@Param("layoffRate") int layoffRate,
@Param("noArrears") boolean noArrears);
/** 稳岗补贴定额:资格成立时为实缴失业保险费的 50% */
double subsidyAmountWan(@Param("paidUiWan") double paidUiWan,
@Param("eligible") boolean eligible);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE javas PUBLIC "-//PAOHAIJIAO//DTD API JAVA 1.0//EN"
"classpath:paohaijiao/dtd/Jquick-java.dtd">
<javas namespace="com.github.paohaijiao.demo.TaxReliefRules">
<!-- 企业所得税:小微分段优惠(100 万以下 5%,100-300 万部分 10%),否则 25% -->
<java name="incomeTaxWan" returnClass="double">
<![CDATA[
double def incomeTaxWan(double:incomeWan, boolean:smallQualified) {
double tax = 0.0;
if (smallQualified) {
if (incomeWan <= 100.0) {
tax = incomeWan * 0.05;
} else {
tax = 5.0 + (incomeWan - 100.0) * 0.10;
}
} else {
tax = incomeWan * 0.25;
}
return tax;
}
]]>
</java>
<!-- 增值税征收率:小规模 1%,一般纳税人不适用此优惠(0 表示不涉及) -->
<java name="vatLevyRate" returnClass="double">
<![CDATA[
double def vatLevyRate(str:taxpayerType) {
if (taxpayerType == "SMALL_SCALE") {
return 0.01;
} else {
return 0.0;
}
}
]]>
</java>
<!-- 稳岗补贴资格:社保 12 月以上、裁员率 5% 以下、无社保欠费 -->
<java name="stabilizationEligible" returnClass="boolean">
<![CDATA[
boolean def stabilizationEligible(int:ssMonths, int:layoffRate, boolean:noArrears) {
if (ssMonths >= 12) {
if (layoffRate <= 5) {
if (noArrears) {
return true;
} else {
return false;
}
} else {
return false;
}
} else {
return false;
}
}
]]>
</java>
<!-- 稳岗补贴定额:实缴失业保险费的 50% -->
<java name="subsidyAmountWan" returnClass="double">
<![CDATA[
double def subsidyAmountWan(double:paidUiWan, boolean:eligible) {
if (eligible) {
return paidUiWan * 0.5;
} else {
return 0.0;
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.TaxReliefRules;
import com.github.paohaijiao.demo.model.TaxReliefApplication;
import com.github.paohaijiao.demo.model.TaxReliefDecision;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 场景 23:小微企业税费减免 / 稳岗补贴资格判定服务
* <p>
* 职责划分:
* <ul>
* <li><b>Java 侧</b>:小型微利企业三项条件(人数/资产/所得额)、行业禁入、
* 欠税冻结、法定税额与减免额对比核算</li>
* <li><b>规则侧</b>:分段实际税负、小规模征收率、稳岗资格与返还比例,外置在 XML 中</li>
* </ul>
*/
public class TaxReliefService {
private static final int EMPLOYEE_LIMIT = 300;
private static final double ASSETS_LIMIT_WAN = 5000.0;
private static final double INCOME_LIMIT_WAN = 300.0;
private final TaxReliefRules rules;
public TaxReliefService(TaxReliefRules rules) {
this.rules = rules;
}
public TaxReliefDecision evaluate(TaxReliefApplication app) {
TaxReliefDecision decision = new TaxReliefDecision();
decision.setEnterpriseId(app.getEnterpriseId());
double income = app.getAnnualTaxableIncomeWan();
double statutory = round2(income * 0.25);
decision.setStatutoryTaxWan(statutory);
if (app.isRestrictedIndustry()) {
decision.setResult("DENIED");
decision.setSmallQualified(false);
decision.setIncomeTaxWan(statutory);
decision.setTaxSavingWan(0.0);
decision.setVatLevyRate(0.0);
decision.setStabilizationEligible(false);
decision.setStabilizationSubsidyWan(0.0);
decision.addReason("企业从事限制或禁止行业,不得享受小型微利企业税收优惠及稳岗返还");
return decision;
}
decision.setResult("APPROVED");
boolean employeeOk = app.getEmployeeCount() <= EMPLOYEE_LIMIT;
boolean assetsOk = app.getTotalAssetsWan() <= ASSETS_LIMIT_WAN;
boolean incomeOk = income <= INCOME_LIMIT_WAN;
boolean qualified = employeeOk && assetsOk && incomeOk;
decision.setSmallQualified(qualified);
double tax = round2(rules.incomeTaxWan(income, qualified));
decision.setIncomeTaxWan(tax);
decision.setTaxSavingWan(round2(statutory - tax));
if (qualified) {
decision.addReason("符合小型微利企业条件,年应纳企业所得税核定为 " + tax + " 万元,"
+ "较法定税率减免 " + decision.getTaxSavingWan() + " 万元");
} else {
decision.addReason("不满足小型微利企业条件,按 25% 法定税率缴纳企业所得税 " + statutory + " 万元");
if (!employeeOk) {
decision.addReason("从业人数 " + app.getEmployeeCount() + " 人超过 300 人标准");
}
if (!assetsOk) {
decision.addReason("资产总额 " + money(app.getTotalAssetsWan()) + " 万元超过 5000 万元标准");
}
if (!incomeOk) {
decision.addReason("年应纳税所得额超过 300 万元标准");
}
}
double vatRate = rules.vatLevyRate(app.getTaxpayerType());
decision.setVatLevyRate(vatRate);
if (vatRate > 0.0) {
decision.addReason("小规模纳税人增值税减按 1% 征收率执行");
}
boolean stable = rules.stabilizationEligible(app.getSocialInsuranceMonths(),
app.getLayoffRate(), !app.isSocialArrears());
if (stable && app.isOverdueTax()) {
stable = false;
decision.addReason("企业存在欠税记录,稳岗返还暂缓拨付,清缴后另行申请");
}
decision.setStabilizationEligible(stable);
double subsidy = round2(rules.subsidyAmountWan(app.getPaidUnemploymentInsuranceWan(), stable));
decision.setStabilizationSubsidyWan(subsidy);
if (stable) {
decision.addReason("符合稳岗返还条件,按实缴失业保险费 50% 核定补贴 " + subsidy + " 万元");
} else if (!app.isOverdueTax()) {
decision.addReason("不满足稳岗返还条件(社保连续缴纳满 12 月、裁员率 5% 以下、无社保欠费)");
}
return decision;
}
private double round2(double value) {
return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
/** 金额文本:固定两位小数且禁用科学计数法 */
private String money(double value) {
return BigDecimal.valueOf(value).setScale(2, RoundingMode.HALF_UP).toPlainString();
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.TaxReliefApplication;
import com.github.paohaijiao.demo.model.TaxReliefDecision;
import com.github.paohaijiao.demo.service.TaxReliefService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 23:小微企业税费减免 / 稳岗补贴资格判定
* <p>
* 覆盖 6 组企业形态:小规模低档税率、一般纳税人跨档税率、三项标准超标、
* 临界值小微企业、限制行业禁入、裁员率与欠税影响稳岗返还。
*/
public class TaxReliefServiceTest {
private TaxReliefService service;
@Before
public void setUp() {
TaxReliefRules rules = JQuickJava.create()
.createApi(TaxReliefRules.class, "demo/tax-relief.xml");
this.service = new TaxReliefService(rules);
}
/**
* 小规模纳税人,80 人、所得额 80 万、稳岗条件齐全 → 所得税 5% 缴 4 万,补贴 2 万
*/
@Test
public void shouldApplyFivePercentTaxAndStabilizationSubsidy() {
TaxReliefApplication app = baseApplication("TR2026001", "SMALL_SCALE");
app.setEmployeeCount(80);
app.setTotalAssetsWan(1000.0);
app.setAnnualTaxableIncomeWan(80.0);
app.setSocialInsuranceMonths(24);
app.setLayoffRate(2);
app.setPaidUnemploymentInsuranceWan(4.0);
TaxReliefDecision decision = service.evaluate(app);
Assert.assertEquals("APPROVED", decision.getResult());
Assert.assertTrue(decision.isSmallQualified());
Assert.assertEquals(20.0, decision.getStatutoryTaxWan(), 0.001);
Assert.assertEquals(4.0, decision.getIncomeTaxWan(), 0.001);
Assert.assertEquals(16.0, decision.getTaxSavingWan(), 0.001);
Assert.assertEquals(0.01, decision.getVatLevyRate(), 0.00001);
Assert.assertTrue(decision.isStabilizationEligible());
Assert.assertEquals(2.0, decision.getStabilizationSubsidyWan(), 0.001);
}
/**
* 一般纳税人,200 人、所得额 250 万跨档 → 5 + 150×10% = 20 万,社保不足无稳岗
*/
@Test
public void applySegmentedTaxForIncomeOverOneMillion() {
TaxReliefApplication app = baseApplication("TR2026002", "GENERAL");
app.setEmployeeCount(200);
app.setTotalAssetsWan(4000.0);
app.setAnnualTaxableIncomeWan(250.0);
app.setSocialInsuranceMonths(6);
app.setLayoffRate(1);
app.setPaidUnemploymentInsuranceWan(3.0);
TaxReliefDecision decision = service.evaluate(app);
Assert.assertTrue(decision.isSmallQualified());
Assert.assertEquals(62.5, decision.getStatutoryTaxWan(), 0.001);
Assert.assertEquals(20.0, decision.getIncomeTaxWan(), 0.001);
Assert.assertEquals(42.5, decision.getTaxSavingWan(), 0.001);
Assert.assertEquals(0.0, decision.getVatLevyRate(), 0.00001);
Assert.assertFalse(decision.isStabilizationEligible());
Assert.assertEquals(0.0, decision.getStabilizationSubsidyWan(), 0.001);
}
/**
* 400 人、资产 6000 万、所得额 400 万,三项标准全超 → 按 25% 完税,逐条告知
*/
@Test
public void shouldDenySmallTaxBenefitWhenAllLimitsExceeded() {
TaxReliefApplication app = baseApplication("TR2026003", "GENERAL");
app.setEmployeeCount(400);
app.setTotalAssetsWan(6000.0);
app.setAnnualTaxableIncomeWan(400.0);
app.setSocialInsuranceMonths(0);
app.setLayoffRate(0);
TaxReliefDecision decision = service.evaluate(app);
Assert.assertFalse(decision.isSmallQualified());
Assert.assertEquals(100.0, decision.getIncomeTaxWan(), 0.001);
Assert.assertEquals(0.0, decision.getTaxSavingWan(), 0.001);
Assert.assertTrue(decision.getReasons().contains("从业人数 400 人超过 300 人标准"));
Assert.assertTrue(decision.getReasons().contains("资产总额 6000.00 万元超过 5000 万元标准"));
Assert.assertTrue(decision.getReasons().contains("年应纳税所得额超过 300 万元标准"));
}
/**
* 人数 300、资产 5000 万、所得额 300 万恰在临界值 → 仍具备小微资格,税 25 万
*/
@Test
public void shouldQualifyAtExactBoundaryValues() {
TaxReliefApplication app = baseApplication("TR2026004", "GENERAL");
app.setEmployeeCount(300);
app.setTotalAssetsWan(5000.0);
app.setAnnualTaxableIncomeWan(300.0);
app.setSocialInsuranceMonths(24);
app.setLayoffRate(0);
TaxReliefDecision decision = service.evaluate(app);
Assert.assertTrue(decision.isSmallQualified());
Assert.assertEquals(75.0, decision.getStatutoryTaxWan(), 0.001);
Assert.assertEquals(25.0, decision.getIncomeTaxWan(), 0.001);
Assert.assertEquals(50.0, decision.getTaxSavingWan(), 0.001);
}
/**
* 限制行业企业即使规模符合 → 全面禁止享受,按法定税率完税
*/
@Test
public void shouldDenyAllBenefitsForRestrictedIndustry() {
TaxReliefApplication app = baseApplication("TR2026005", "SMALL_SCALE");
app.setEmployeeCount(50);
app.setTotalAssetsWan(500.0);
app.setAnnualTaxableIncomeWan(80.0);
app.setSocialInsuranceMonths(36);
app.setLayoffRate(0);
app.setPaidUnemploymentInsuranceWan(2.0);
app.setRestrictedIndustry(true);
TaxReliefDecision decision = service.evaluate(app);
Assert.assertEquals("DENIED", decision.getResult());
Assert.assertFalse(decision.isSmallQualified());
Assert.assertEquals(20.0, decision.getIncomeTaxWan(), 0.001);
Assert.assertEquals(0.0, decision.getTaxSavingWan(), 0.001);
Assert.assertEquals(0.0, decision.getVatLevyRate(), 0.00001);
Assert.assertFalse(decision.isStabilizationEligible());
Assert.assertTrue(decision.getReasons().contains(
"企业从事限制或禁止行业,不得享受小型微利企业税收优惠及稳岗返还"));
}
/**
* 裁员率 8% 不符稳岗条件;裁员率达标但欠税的,稳岗返还暂缓
*/
@Test
public void shouldBlockStabilizationSubsidyForHighLayoffAndTaxDue() {
TaxReliefApplication highLayoff = baseApplication("TR2026006", "SMALL_SCALE");
highLayoff.setEmployeeCount(100);
highLayoff.setTotalAssetsWan(800.0);
highLayoff.setAnnualTaxableIncomeWan(60.0);
highLayoff.setSocialInsuranceMonths(24);
highLayoff.setLayoffRate(8);
highLayoff.setPaidUnemploymentInsuranceWan(4.0);
TaxReliefDecision d1 = service.evaluate(highLayoff);
Assert.assertTrue(d1.isSmallQualified());
Assert.assertEquals(3.0, d1.getIncomeTaxWan(), 0.001);
Assert.assertFalse(d1.isStabilizationEligible());
Assert.assertEquals(0.0, d1.getStabilizationSubsidyWan(), 0.001);
TaxReliefApplication taxDue = baseApplication("TR2026007", "SMALL_SCALE");
taxDue.setEmployeeCount(100);
taxDue.setTotalAssetsWan(800.0);
taxDue.setAnnualTaxableIncomeWan(60.0);
taxDue.setSocialInsuranceMonths(24);
taxDue.setLayoffRate(2);
taxDue.setPaidUnemploymentInsuranceWan(4.0);
taxDue.setOverdueTax(true);
TaxReliefDecision d2 = service.evaluate(taxDue);
Assert.assertFalse(d2.isStabilizationEligible());
Assert.assertTrue(d2.getReasons().contains("企业存在欠税记录,稳岗返还暂缓拨付,清缴后另行申请"));
}
private TaxReliefApplication baseApplication(String id, String taxpayerType) {
TaxReliefApplication app = new TaxReliefApplication();
app.setEnterpriseId(id);
app.setEnterpriseName("测试小微企业");
app.setTaxpayerType(taxpayerType);
app.setEmployeeCount(100);
app.setTotalAssetsWan(1000.0);
app.setAnnualTaxableIncomeWan(100.0);
app.setRestrictedIndustry(false);
app.setSocialInsuranceMonths(12);
app.setLayoffRate(0);
app.setSocialArrears(false);
app.setOverdueTax(false);
app.setPaidUnemploymentInsuranceWan(0.0);
return app;
}
}

Run from the project root:

Terminal window
mvn test -Dtest=TaxReliefServiceTest
Test method Business scenario constructed Key assertions / expected decision
shouldApplyFivePercentTaxAndStabilizationSubsidy Small-scale taxpayer, 80 employees, 10M yuan assets, 800k yuan income, 24 months of social insurance, 2% layoffs, 40k yuan unemployment insurance paid APPROVED; statutory 200k, assessed 40k, relief 160k; levy rate 0.01; job retention eligible with 20k subsidy
applySegmentedTaxForIncomeOverOneMillion General taxpayer, 200 employees, 40M yuan assets, 2.5M yuan income, only 6 months of social insurance SME qualified; statutory 625k, assessed 200k (5 + 150x10%), relief 425k; levy rate 0; job retention not eligible, subsidy 0
shouldDenySmallTaxBenefitWhenAllLimitsExceeded 400 employees, 60M yuan assets, 4M yuan income — all three criteria exceeded smallQualified=false; 1M paid at 25%, relief 0; reasons itemized: “400 人超过 300 人”, “资产总额 6000.00 万元超过 5000 万元”, “所得额超过 300 万元”
shouldQualifyAtExactBoundaryValues 300 employees, 50M yuan assets, 3M yuan income — all exactly at the boundary Boundary values are inclusive, so eligibility holds; statutory 750k, assessed 250k, relief 500k
shouldDenyAllBenefitsForRestrictedIndustry Small-scale taxpayer, 50 employees, 5M yuan assets, 800k yuan income, but in a restricted industry DENIED; income tax 200k, relief 0, levy rate 0, job retention false; reasons contain the industry exclusion note
shouldBlockStabilizationSubsidyForHighLayoffAndTaxDue Two sub-scenarios: 24 months of social insurance but 8% layoff rate; 2% layoff rate but overdue taxes The former has job retention false and subsidy 0 (income tax still enjoys 30k); the latter has job retention false with reasons containing “稳岗返还暂缓拨付,清缴后另行申请”
  • Tax rates and subsidy rules fully externalized to XML: the 5%/10% segmented burden, the 25% statutory rate, the 1% levy rate, the 12-month / 5% job-retention conditions, and the 50% rebate ratio all live in the script; annual tax policy adjustments only require editing the XML and rebuilding the proxy, with no release of the main collection system.
  • Progressive excess segments computed in one pass inside the script: incomeTaxWan computes income above 1 million yuan as “5.0 + excess x10%”, avoiding the common Java-side lookup-table mistake of charging the full amount at 10%.
  • Industry exclusion and overdue-tax suspension short-circuit on the Java side: restricted industries are directly DENIED before any computation with all three preferences zeroed; overdue-tax subjects have their job-retention eligibility overridden after the rule check, distinguishing the two policy wordings “not eligible” and “suspended payment”.
  • Positive boolean conditions adapted to script syntax: the script does not support ! negation, so the Service converts with !app.isSocialArrears() on the Java side and passes a positive noArrears, while the rule only uses nested positive ifs returning true/false.
  • Amount precision and document wording guaranteed on the Java side: round2 uses BigDecimal HALF_UP for two decimals, and money uses toPlainString to fix two decimals and disable scientific notation, keeping document wording like “资产总额 6000.00 万元” well-formed.
  • Itemized, auditable failure reasons open to reconsideration: each of the three scale conditions is judged independently with its reason appended; statutory tax, relief amount, levy rate, subsidy amount, and their bases all go into reasons, ready to generate the enterprise-benefit fulfillment notice directly.