You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
6.8 KiB
150 lines
6.8 KiB
package com.mydreamjob.salarycalculator;
|
|
|
|
import com.mydreamjob.common.exception.BusinessException;
|
|
import com.mydreamjob.common.exception.ErrorCode;
|
|
import org.junit.jupiter.api.Test;
|
|
|
|
import java.math.BigDecimal;
|
|
import java.time.LocalDate;
|
|
|
|
import static org.assertj.core.api.Assertions.assertThat;
|
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
|
|
|
class SalaryCalculationEngineTest {
|
|
|
|
private final SalaryCalculationEngine engine = new SalaryCalculationEngine();
|
|
|
|
@Test
|
|
void calculatesBaseCaseWithCumulativeWithholding() {
|
|
SalaryCalculationResult result = engine.calculate(input("10000", 1, "0", null), policy());
|
|
|
|
assertThat(result.getSocialSecurityBase()).isEqualByComparingTo("10000.00");
|
|
assertThat(result.getHousingFundBase()).isEqualByComparingTo("10000.00");
|
|
assertThat(result.getPension()).isEqualByComparingTo("800.00");
|
|
assertThat(result.getMedical()).isEqualByComparingTo("200.00");
|
|
assertThat(result.getUnemployment()).isEqualByComparingTo("50.00");
|
|
assertThat(result.getHousingFund()).isEqualByComparingTo("1200.00");
|
|
assertThat(result.getCurrentMonthTax()).isEqualByComparingTo("82.50");
|
|
assertThat(result.getNetSalary()).isEqualByComparingTo("7667.50");
|
|
assertThat(result.isEstimate()).isTrue();
|
|
assertThat(result.getDataLevel()).isEqualTo(SalaryDataLevel.CITY);
|
|
}
|
|
|
|
@Test
|
|
void clampsBothBasesToFloorAndCeiling() {
|
|
SalaryCalculationResult low = engine.calculate(input("3000", 1, "0", null), policy());
|
|
SalaryCalculationResult high = engine.calculate(input("30000", 1, "0", null), policy());
|
|
|
|
assertThat(low.getSocialSecurityBase()).isEqualByComparingTo("5000.00");
|
|
assertThat(low.getHousingFundBase()).isEqualByComparingTo("5000.00");
|
|
assertThat(low.getCurrentMonthTax()).isEqualByComparingTo("0.00");
|
|
assertThat(low.getNetSalary()).isEqualByComparingTo("1875.00");
|
|
|
|
assertThat(high.getSocialSecurityBase()).isEqualByComparingTo("20000.00");
|
|
assertThat(high.getHousingFundBase()).isEqualByComparingTo("20000.00");
|
|
assertThat(high.getCurrentMonthTax()).isEqualByComparingTo("615.00");
|
|
assertThat(high.getNetSalary()).isEqualByComparingTo("24885.00");
|
|
}
|
|
|
|
@Test
|
|
void usesPriorCumulativeTaxForTwelfthMonth() {
|
|
SalaryCalculationResult result = engine.calculate(input("20000", 12, "0", null), policy());
|
|
|
|
assertThat(result.getCumulativeTaxableIncome()).isEqualByComparingTo("126000.00");
|
|
assertThat(result.getCumulativeTaxDue()).isEqualByComparingTo("10080.00");
|
|
assertThat(result.getPreviouslyWithheldTax()).isEqualByComparingTo("9030.00");
|
|
assertThat(result.getCurrentMonthTax()).isEqualByComparingTo("1050.00");
|
|
assertThat(result.getNetSalary()).isEqualByComparingTo("14450.00");
|
|
}
|
|
|
|
@Test
|
|
void appliesSpecialDeductionAndOverrides() {
|
|
SalaryOverrideParameters overrides = SalaryOverrideParameters.builder()
|
|
.socialSecurityBase(new BigDecimal("8000.00"))
|
|
.housingFundBase(new BigDecimal("10000.00"))
|
|
.pensionRate(new BigDecimal("0.08"))
|
|
.medicalRate(new BigDecimal("0.02"))
|
|
.unemploymentRate(new BigDecimal("0.005"))
|
|
.housingFundRate(new BigDecimal("0.12"))
|
|
.seriousIllnessMonthlyFee(BigDecimal.ZERO)
|
|
.build();
|
|
|
|
SalaryCalculationResult result = engine.calculate(
|
|
input("10000", 1, "2000", overrides), policy());
|
|
|
|
assertThat(result.getSocialSecurityBase()).isEqualByComparingTo("8000.00");
|
|
assertThat(result.getPension()).isEqualByComparingTo("640.00");
|
|
assertThat(result.getCurrentMonthTax()).isEqualByComparingTo("28.80");
|
|
assertThat(result.getNetSalary()).isEqualByComparingTo("7931.20");
|
|
}
|
|
|
|
@Test
|
|
void rejectsOverrideOutsideCityBaseRange() {
|
|
SalaryOverrideParameters overrides = SalaryOverrideParameters.builder()
|
|
.socialSecurityBase(new BigDecimal("4999.99"))
|
|
.build();
|
|
|
|
assertThatThrownBy(() -> engine.calculate(input("10000", 1, "0", overrides), policy()))
|
|
.isInstanceOf(BusinessException.class)
|
|
.extracting(ex -> ((BusinessException) ex).getErrorCodeInt())
|
|
.isEqualTo(ErrorCode.SALARY_INVALID_PARAMETER.getCode());
|
|
}
|
|
|
|
@Test
|
|
void zeroContributionRateAllowsZeroBase() {
|
|
SalaryOverrideParameters overrides = SalaryOverrideParameters.builder()
|
|
.socialSecurityBase(BigDecimal.ZERO)
|
|
.housingFundBase(BigDecimal.ZERO)
|
|
.pensionRate(BigDecimal.ZERO)
|
|
.medicalRate(BigDecimal.ZERO)
|
|
.unemploymentRate(BigDecimal.ZERO)
|
|
.housingFundRate(BigDecimal.ZERO)
|
|
.seriousIllnessMonthlyFee(BigDecimal.ZERO)
|
|
.build();
|
|
|
|
SalaryCalculationResult result = engine.calculate(
|
|
input("10000", 1, "0", overrides), policy());
|
|
|
|
assertThat(result.getPension()).isEqualByComparingTo("0.00");
|
|
assertThat(result.getMedical()).isEqualByComparingTo("0.00");
|
|
assertThat(result.getUnemployment()).isEqualByComparingTo("0.00");
|
|
assertThat(result.getHousingFund()).isEqualByComparingTo("0.00");
|
|
assertThat(result.getCurrentMonthTax()).isEqualByComparingTo("150.00");
|
|
assertThat(result.getNetSalary()).isEqualByComparingTo("9850.00");
|
|
}
|
|
|
|
private SalaryCalculationInput input(
|
|
String gross, int monthIndex, String specialDeduction,
|
|
SalaryOverrideParameters overrides) {
|
|
return SalaryCalculationInput.builder()
|
|
.grossMonthlySalary(new BigDecimal(gross))
|
|
.monthIndex(monthIndex)
|
|
.specialAdditionalDeduction(new BigDecimal(specialDeduction))
|
|
.overrides(overrides)
|
|
.build();
|
|
}
|
|
|
|
private SalaryCityPolicy policy() {
|
|
return SalaryCityPolicy.builder()
|
|
.cityCode("110000")
|
|
.provinceCode("110000")
|
|
.provinceName("北京市")
|
|
.cityName("北京市")
|
|
.policyVersion("2026-09-20-v1")
|
|
.policyEffectiveDate(LocalDate.of(2026, 7, 1))
|
|
.socialSecurityBaseMin(new BigDecimal("5000.00"))
|
|
.socialSecurityBaseMax(new BigDecimal("20000.00"))
|
|
.housingFundBaseMin(new BigDecimal("5000.00"))
|
|
.housingFundBaseMax(new BigDecimal("20000.00"))
|
|
.pensionRate(new BigDecimal("0.080000"))
|
|
.medicalRate(new BigDecimal("0.020000"))
|
|
.unemploymentRate(new BigDecimal("0.005000"))
|
|
.housingFundRate(new BigDecimal("0.120000"))
|
|
.seriousIllnessMonthlyFee(BigDecimal.ZERO)
|
|
.dataLevel(SalaryDataLevel.CITY)
|
|
.enabled(true)
|
|
.build();
|
|
}
|
|
}
|
|
|
|
|
|
|