Skip to content

Case 35: Payroll allowance calculation

This case corresponds to scenario 35 of JQuick-Java USE-CASES (requirement 43: automatic payroll allowance calculation), in the HR / compensation-management domain. The decision automatically calculates eight allowance items from an employee’s monthly attendance and performance attributes, applies compliance deductions, and aggregates the allowance total and gross pay.

Monthly payroll processing must aggregate multiple policy-driven and attendance-driven allowances. The input model PayrollInput carries 14 attributes: employee ID, name, base salary, years of service with the company, night-shift count for the month, payroll month, high-temperature workdays and whether the post is outdoors, overtime hours and the overtime hourly base, overtime type (WORKDAY / WEEKEND rest day / HOLIDAY statutory holiday), meal-allowance attendance days, whether the post is in a remote area, whether attendance is perfect, and the monthly performance score.

Each of the eight allowances has an independent criterion, all externalized in the XML script: the seniority allowance is banded at 20/10/5/1 years (8%, 5%, 3%, 1%); night-shift subsidy is 50 yuan per shift; high-temperature allowance is 30 yuan/day outdoors and 15 yuan/day indoors; overtime pay is 1.5x on workdays, 2x on rest days, 3x on statutory holidays; meal subsidy is 30 yuan/day; the remote-area allowance is 10% of base salary; the perfect-attendance bonus is 300 yuan; the performance bonus is banded at 95/85/70/60 points (30%, 20%, 10%, 5%). The orchestration layer adds two compliance gates: the high-temperature allowance is only paid June through September — claims in other months are zeroed out with an anti-mispayment note; monthly overtime beyond the statutory 36-hour cap is deducted with a note, and only 36 hours are paid.

The decision result is carried by PayrollDecision: the eight allowance item amounts, the allowance total totalAllowance, gross pay grossPay (base salary plus allowances), and the notes list of calculation remarks. All amounts are rounded to two decimals with BigDecimal on the Java side, avoiding cent-level drift from raw double addition.

  • Allowance policies adjust frequently by region and year: high-temperature allowance standards, night-shift unit prices, performance band coefficients, and seniority ratios differ across provinces and years; hardcoded in the payroll system, every adjustment must squeeze a release before month-end closing.
  • Eight allowances coupled in one payroll formula: any single-item standard change forces a full regression of the whole calculation; HR and finance cannot tune or review each item’s criterion independently.
  • Compliance deductions are easily missed: the high-temperature payment window and the statutory 36-hour monthly overtime cap are hard compliance requirements; buried inside calculation scripts they can be bypassed by claim data, causing mispayments and labor-compliance risk.
  • Amount precision and auditability requirements are high: raw double accumulation produces cent-level tail differences, while every cent on an employee’s payslip must be traceable to a criterion and deduction note.
  • Legacy HR systems and IT-innovation environments cannot host heavy platforms: many payroll systems still run on JDK 8 and domestic middleware, so introducing a heavyweight HR rule platform just for allowance rules is not appropriate.

This case uses the standard JQuick-Java six-piece set: input model, decision result model, Rules interface, XML rule script, Service orchestration, and JUnit tests. All code is verified on Oracle JDK 1.8.

Layer Responsibility Files
Domain model Carries monthly payroll attributes and the eight-item plus totals decision PayrollInput.java, PayrollDecision.java
Rule layer One method per allowance; ratios/unit prices/bands all externalized SalaryAllowanceRules.java, salary-allowance.xml
Orchestration layer High-temperature off-season anti-mispayment, 36-hour overtime cap deduction, two-decimal rounding and totals SalaryAllowanceService.java
Tests 6 real-world payroll profiles SalaryAllowanceServiceTest.java
Rule method Criteria Score/output
seniorityAllowance ≥ 20 years pays 8% of base salary; ≥ 10 years 5%; ≥ 5 years 3%; ≥ 1 year 1%; under 1 year 0 base salary × 0.08/0.05/0.03/0.01
nightShiftAllowance 50 yuan per shift × night-shift count shifts × 50 (yuan)
highTempAllowance 30 yuan/day for outdoor posts, 15 yuan/day for indoor posts days × 30 or days × 15 (yuan)
overtimePay 3x statutory holiday, 2x rest day, 1.5x workday: hourly base × hours × multiplier double amount
mealAllowance 30 yuan/day × meal-allowance attendance days days × 30 (yuan)
remoteAreaAllowance 10% of base salary for remote-area posts, otherwise 0 base salary × 0.10 or 0
perfectAttendanceBonus 300 yuan for perfect attendance, otherwise 0 300 or 0 (yuan)
performanceBonus Score ≥ 95 pays 30%; ≥ 85 pays 20%; ≥ 70 pays 10%; ≥ 60 pays 5%; otherwise 0 base salary × 0.30/0.20/0.10/0.05

Place the two model classes under src/test/java/com/github/paohaijiao/demo/model/, the rules interface under demo/, the Service under demo/service/, the XML script under src/test/resources/demo/, and the test class under demo/, keeping the package names exactly as in the source. Run mvn test -Dtest=SalaryAllowanceServiceTest from the project root to reproduce all 6 cases.

package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
/**
* 员工月度薪资补贴核算要素
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class PayrollInput {
/**
* 工号
*/
private String employeeId;
/**
* 姓名
*/
private String employeeName;
/**
* 基本工资
*/
private double baseSalary;
/**
* 本单位工龄(年)
*/
private int yearsOfService;
/**
* 当月夜班次数
*/
private int nightShifts;
/**
* 工资所属月份(1-12)
*/
private int payrollMonth;
/**
* 高温作业天数
*/
private int highTempDays;
/**
* 是否户外高温岗位
*/
private boolean highTempOutdoor;
/**
* 加班时长(小时)
*/
private double overtimeHours;
/**
* 加班小时基数(元/小时)
*/
private double overtimeHourlyBase;
/**
* 加班类型:WORKDAY 工作日 / WEEKEND 休息日 / HOLIDAY 法定节假日
*/
private String overtimeType;
/**
* 出勤餐补天数
*/
private int mealAllowanceDays;
/**
* 是否边远地区岗位
*/
private boolean remoteArea;
/**
* 是否全勤
*/
private boolean attendancePerfect;
/**
* 月度绩效分(0-100)
*/
private int performanceScore;
}
package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 月度补贴分项与应发合计
*/
@Data
public class PayrollDecision {
/**
* 工号
*/
private String employeeId;
private double seniorityAllowance;
private double nightShiftAllowance;
private double highTempAllowance;
private double overtimePay;
private double mealAllowance;
private double remoteAreaAllowance;
private double perfectAttendanceBonus;
private double performanceBonus;
/**
* 补贴合计
*/
private double totalAllowance;
/**
* 应发工资(基本工资 + 补贴)
*/
private double grossPay;
/**
* 核算说明(防错发、超限提示)
*/
private List<String> notes = new ArrayList<String>();
public void addNote(String note) {
this.notes.add(note);
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/**
* 场景 35:薪资补贴各分项核算规则
*/
public interface SalaryAllowanceRules {
/**
* 工龄津贴:20 年 8%、10 年 5%、5 年 3%、满 1 年 1%
*/
double seniorityAllowance(@Param("baseSalary") double baseSalary,
@Param("years") int years);
/**
* 夜班补贴:50 元/班
*/
int nightShiftAllowance(@Param("shifts") int shifts);
/**
* 高温津贴:户外 30 元/天,室内 15 元/天
*/
int highTempAllowance(@Param("days") int days,
@Param("outdoor") boolean outdoor);
/**
* 加班工资:工作日 1.5 倍、休息日 2 倍、法定节假日 3 倍
*/
double overtimePay(@Param("hourlyBase") double hourlyBase,
@Param("hours") double hours,
@Param("type") String type);
/**
* 餐补:30 元/天
*/
int mealAllowance(@Param("days") int days);
/**
* 边远地区津贴:基本工资 10%
*/
double remoteAreaAllowance(@Param("baseSalary") double baseSalary,
@Param("remote") boolean remote);
/**
* 全勤奖:300 元
*/
int perfectAttendanceBonus(@Param("perfect") boolean perfect);
/**
* 绩效奖金:≥95 30%、≥85 20%、≥70 10%、≥60 5%,其余为 0
*/
double performanceBonus(@Param("baseSalary") double baseSalary,
@Param("score") int score);
}
<?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.SalaryAllowanceRules">
<java name="seniorityAllowance" returnClass="double">
<![CDATA[
double def seniorityAllowance(double:baseSalary, int:years) {
if (years >= 20) {
return baseSalary * 0.08;
} else if (years >= 10) {
return baseSalary * 0.05;
} else if (years >= 5) {
return baseSalary * 0.03;
} else if (years >= 1) {
return baseSalary * 0.01;
} else {
return 0.0;
}
}
]]>
</java>
<java name="nightShiftAllowance" returnClass="int">
<![CDATA[
int def nightShiftAllowance(int:shifts) {
return shifts * 50;
}
]]>
</java>
<java name="highTempAllowance" returnClass="int">
<![CDATA[
int def highTempAllowance(int:days, boolean:outdoor) {
if (outdoor) {
return days * 30;
} else {
return days * 15;
}
}
]]>
</java>
<java name="overtimePay" returnClass="double">
<![CDATA[
double def overtimePay(double:hourlyBase, double:hours, str:type) {
if (type == "HOLIDAY") {
return hourlyBase * hours * 3.0;
} else if (type == "WEEKEND") {
return hourlyBase * hours * 2.0;
} else {
return hourlyBase * hours * 1.5;
}
}
]]>
</java>
<java name="mealAllowance" returnClass="int">
<![CDATA[
int def mealAllowance(int:days) {
return days * 30;
}
]]>
</java>
<java name="remoteAreaAllowance" returnClass="double">
<![CDATA[
double def remoteAreaAllowance(double:baseSalary, boolean:remote) {
if (remote) {
return baseSalary * 0.10;
} else {
return 0.0;
}
}
]]>
</java>
<java name="perfectAttendanceBonus" returnClass="int">
<![CDATA[
int def perfectAttendanceBonus(boolean:perfect) {
if (perfect) {
return 300;
} else {
return 0;
}
}
]]>
</java>
<java name="performanceBonus" returnClass="double">
<![CDATA[
double def performanceBonus(double:baseSalary, int:score) {
if (score >= 95) {
return baseSalary * 0.30;
} else if (score >= 85) {
return baseSalary * 0.20;
} else if (score >= 70) {
return baseSalary * 0.10;
} else if (score >= 60) {
return baseSalary * 0.05;
} else {
return 0.0;
}
}
]]>
</java>
</javas>
package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.SalaryAllowanceRules;
import com.github.paohaijiao.demo.model.PayrollDecision;
import com.github.paohaijiao.demo.model.PayrollInput;
import java.math.BigDecimal;
import java.math.RoundingMode;
/**
* 场景 35:薪资补贴自动核算(工龄/夜班/高温/加班/餐补/边远/全勤/绩效)
*/
public class SalaryAllowanceService {
/**
* 法定月度加班时长上限(小时)
*/
private static final double MAX_LEGAL_OVERTIME_HOURS = 36.0;
private final SalaryAllowanceRules rules;
public SalaryAllowanceService(SalaryAllowanceRules rules) {
this.rules = rules;
}
public PayrollDecision calculate(PayrollInput input) {
PayrollDecision d = new PayrollDecision();
d.setEmployeeId(input.getEmployeeId());
d.setSeniorityAllowance(money(rules.seniorityAllowance(
input.getBaseSalary(), input.getYearsOfService())));
d.setNightShiftAllowance(rules.nightShiftAllowance(input.getNightShifts()));
if (input.getHighTempDays() > 0 && (input.getPayrollMonth() < 6 || input.getPayrollMonth() > 9)) {
d.setHighTempAllowance(0.0);
d.addNote("申报高温作业 " + input.getHighTempDays()
+ " 天,但 " + input.getPayrollMonth()
+ " 月不在高温津贴发放期(6-9 月),该补贴不予核发");
} else {
d.setHighTempAllowance(rules.highTempAllowance(
input.getHighTempDays(), input.isHighTempOutdoor()));
}
double payableOvertimeHours = input.getOvertimeHours();
if (payableOvertimeHours > MAX_LEGAL_OVERTIME_HOURS) {
d.addNote("申报加班 " + input.getOvertimeHours()
+ " 小时超出法定上限 36 小时,超出部分按 "
+ (payableOvertimeHours - MAX_LEGAL_OVERTIME_HOURS) + " 小时核减");
payableOvertimeHours = MAX_LEGAL_OVERTIME_HOURS;
}
d.setOvertimePay(money(rules.overtimePay(input.getOvertimeHourlyBase(),
payableOvertimeHours, input.getOvertimeType())));
d.setMealAllowance(rules.mealAllowance(input.getMealAllowanceDays()));
d.setRemoteAreaAllowance(money(rules.remoteAreaAllowance(
input.getBaseSalary(), input.isRemoteArea())));
d.setPerfectAttendanceBonus(rules.perfectAttendanceBonus(input.isAttendancePerfect()));
d.setPerformanceBonus(money(rules.performanceBonus(
input.getBaseSalary(), input.getPerformanceScore())));
double total = money(d.getSeniorityAllowance() + d.getNightShiftAllowance()
+ d.getHighTempAllowance() + d.getOvertimePay() + d.getMealAllowance()
+ d.getRemoteAreaAllowance() + d.getPerfectAttendanceBonus()
+ d.getPerformanceBonus());
d.setTotalAllowance(total);
d.setGrossPay(money(input.getBaseSalary() + total));
return d;
}
private double money(double v) {
return BigDecimal.valueOf(v).setScale(2, RoundingMode.HALF_UP).doubleValue();
}
}
package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.PayrollDecision;
import com.github.paohaijiao.demo.model.PayrollInput;
import com.github.paohaijiao.demo.service.SalaryAllowanceService;
import com.github.paohaijiao.xml.JQuickJava;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
/**
* 场景 35:薪资补贴自动核算
* <p>
* 6 组形态:全补贴标杆、新人基础补贴、法定节假日三倍加班、
* 非高温季防错发、加班超 36 小时核减、绩效不达标与 60 分临界。
*/
public class SalaryAllowanceServiceTest {
private SalaryAllowanceService service;
@Before
public void setUp() {
SalaryAllowanceRules rules = JQuickJava.create()
.createApi(SalaryAllowanceRules.class, "demo/salary-allowance.xml");
this.service = new SalaryAllowanceService(rules);
}
/**
* 12 年工龄、8 夜班、12 户外高温、10 小时平日加班、边远、全勤、90 分绩效
*/
@Test
public void shouldCalculateAllAllowanceItems() {
PayrollInput i = base("E001", 10000, 7);
i.setYearsOfService(12);
i.setNightShifts(8);
i.setHighTempDays(12);
i.setHighTempOutdoor(true);
i.setOvertimeHours(10);
i.setOvertimeHourlyBase(60);
i.setOvertimeType("WORKDAY");
i.setMealAllowanceDays(22);
i.setRemoteArea(true);
i.setAttendancePerfect(true);
i.setPerformanceScore(90);
PayrollDecision d = service.calculate(i);
Assert.assertEquals(500.0, d.getSeniorityAllowance(), 0.0001);
Assert.assertEquals(400.0, d.getNightShiftAllowance(), 0.0001);
Assert.assertEquals(360.0, d.getHighTempAllowance(), 0.0001);
Assert.assertEquals(900.0, d.getOvertimePay(), 0.0001);
Assert.assertEquals(660.0, d.getMealAllowance(), 0.0001);
Assert.assertEquals(1000.0, d.getRemoteAreaAllowance(), 0.0001);
Assert.assertEquals(300.0, d.getPerfectAttendanceBonus(), 0.0001);
Assert.assertEquals(2000.0, d.getPerformanceBonus(), 0.0001);
Assert.assertEquals(6120.0, d.getTotalAllowance(), 0.0001);
Assert.assertEquals(16120.0, d.getGrossPay(), 0.0001);
}
/**
* 新人无工龄/夜班/加班/全勤,仅餐补与 72 分绩效
*/
@Test
public void shouldCalculateFreshmanBasicAllowance() {
PayrollInput i = base("E002", 6000, 6);
i.setYearsOfService(0);
i.setMealAllowanceDays(20);
i.setPerformanceScore(72);
PayrollDecision d = service.calculate(i);
Assert.assertEquals(0.0, d.getSeniorityAllowance(), 0.0001);
Assert.assertEquals(600.0, d.getMealAllowance(), 0.0001);
Assert.assertEquals(600.0, d.getPerformanceBonus(), 0.0001);
Assert.assertEquals(1200.0, d.getTotalAllowance(), 0.0001);
Assert.assertEquals(7200.0, d.getGrossPay(), 0.0001);
}
/**
* 6 年工龄 + 4 夜班 + 法定节假日 8 小时三倍加班 + 85 分绩效
*/
@Test
public void shouldPayTripleOvertimeOnHoliday() {
PayrollInput i = base("E003", 8000, 10);
i.setYearsOfService(6);
i.setNightShifts(4);
i.setHighTempDays(0);
i.setOvertimeHours(8);
i.setOvertimeHourlyBase(50);
i.setOvertimeType("HOLIDAY");
i.setMealAllowanceDays(0);
i.setAttendancePerfect(true);
i.setPerformanceScore(85);
PayrollDecision d = service.calculate(i);
Assert.assertEquals(240.0, d.getSeniorityAllowance(), 0.0001);
Assert.assertEquals(200.0, d.getNightShiftAllowance(), 0.0001);
Assert.assertEquals(1200.0, d.getOvertimePay(), 0.0001);
Assert.assertEquals(300.0, d.getPerfectAttendanceBonus(), 0.0001);
Assert.assertEquals(1600.0, d.getPerformanceBonus(), 0.0001);
Assert.assertEquals(3540.0, d.getTotalAllowance(), 0.0001);
}
/**
* 10 月申报 10 天户外高温 → 非发放期防错发清零
*/
@Test
public void shouldRejectHighTempAllowanceOutsideSummer() {
PayrollInput i = base("E004", 7000, 10);
i.setYearsOfService(2);
i.setHighTempDays(10);
i.setHighTempOutdoor(true);
i.setMealAllowanceDays(10);
i.setPerformanceScore(60);
PayrollDecision d = service.calculate(i);
Assert.assertEquals(0.0, d.getHighTempAllowance(), 0.0001);
Assert.assertEquals(70.0, d.getSeniorityAllowance(), 0.0001);
Assert.assertEquals(300.0, d.getMealAllowance(), 0.0001);
Assert.assertEquals(350.0, d.getPerformanceBonus(), 0.0001);
Assert.assertEquals(720.0, d.getTotalAllowance(), 0.0001);
Assert.assertTrue(d.getNotes().get(0).contains("不在高温津贴发放期(6-9 月)"));
}
/**
* 申报 40 小时工作日加班 → 仅核发 36 小时
*/
@Test
public void shouldCapOvertimeAtLegalLimit() {
PayrollInput i = base("E005", 6000, 8);
i.setYearsOfService(0);
i.setOvertimeHours(40);
i.setOvertimeHourlyBase(50);
i.setOvertimeType("WORKDAY");
i.setMealAllowanceDays(0);
i.setPerformanceScore(0);
PayrollDecision d = service.calculate(i);
Assert.assertEquals(2700.0, d.getOvertimePay(), 0.0001);
Assert.assertEquals(2700.0, d.getTotalAllowance(), 0.0001);
Assert.assertTrue(d.getNotes().get(0).contains("超出法定上限 36 小时,超出部分按 4.0 小时核减"));
}
/**
* 绩效 59 分无奖金;同条件 60 分临界拿 5%
*/
@Test
public void shouldHandlePerformanceThresholdAtSixty() {
PayrollInput low = base("E006", 9000, 9);
low.setYearsOfService(10);
low.setNightShifts(10);
low.setMealAllowanceDays(21);
low.setPerformanceScore(59);
PayrollDecision d1 = service.calculate(low);
Assert.assertEquals(450.0, d1.getSeniorityAllowance(), 0.0001);
Assert.assertEquals(500.0, d1.getNightShiftAllowance(), 0.0001);
Assert.assertEquals(630.0, d1.getMealAllowance(), 0.0001);
Assert.assertEquals(0.0, d1.getPerformanceBonus(), 0.0001);
Assert.assertEquals(1580.0, d1.getTotalAllowance(), 0.0001);
PayrollInput edge = base("E007", 9000, 9);
edge.setYearsOfService(10);
edge.setNightShifts(10);
edge.setMealAllowanceDays(21);
edge.setPerformanceScore(60);
PayrollDecision d2 = service.calculate(edge);
Assert.assertEquals(450.0, d2.getPerformanceBonus(), 0.0001);
Assert.assertEquals(2030.0, d2.getTotalAllowance(), 0.0001);
}
private PayrollInput base(String id, double baseSalary, int month) {
PayrollInput i = new PayrollInput();
i.setEmployeeId(id);
i.setEmployeeName("测试员工");
i.setBaseSalary(baseSalary);
i.setYearsOfService(0);
i.setNightShifts(0);
i.setPayrollMonth(month);
i.setHighTempDays(0);
i.setHighTempOutdoor(false);
i.setOvertimeHours(0);
i.setOvertimeHourlyBase(0);
i.setOvertimeType("WORKDAY");
i.setMealAllowanceDays(0);
i.setRemoteArea(false);
i.setAttendancePerfect(false);
i.setPerformanceScore(0);
return i;
}
}

Run from the project root:

Terminal window
mvn test -Dtest=SalaryAllowanceServiceTest

Test coverage (6 @Test methods covering the full-item benchmark, newcomer, triple overtime, anti-mispayment, statutory cap deduction, and the performance threshold):

Test method Business profile Key assertions / expected decision
shouldCalculateAllAllowanceItems E001, base salary 10000, July: 12 years of service, 8 night shifts, 12 outdoor high-temperature days, 10 workday overtime hours (base 60), 22 meal days, remote area, perfect attendance, performance 90 Eight items in order 500/400/360/900/660/1000/300/2000, allowance total 6120.0, gross pay 16120.0
shouldCalculateFreshmanBasicAllowance E002, base salary 6000, June: 0 years of service, no night shifts/overtime, 20 meal days, performance 72 Seniority 0, meal 600, performance 600 (10% band), total 1200.0, gross pay 7200.0
shouldPayTripleOvertimeOnHoliday E003, base salary 8000, October: 6 years of service, 4 night shifts, 8 statutory-holiday overtime hours (base 50), perfect attendance, performance 85 Seniority 240, night shift 200, overtime 1200 (50×8×3), attendance 300, performance 1600, total 3540.0
shouldRejectHighTempAllowanceOutsideSummer E004, base salary 7000, October: 2 years of service, 10 outdoor high-temperature days claimed, 10 meal days, performance 60 High-temperature allowance 0.0, seniority 70, meal 300, performance 350 (5% band), total 720.0; first note contains “不在高温津贴发放期(6-9 月)”
shouldCapOvertimeAtLegalLimit E005, base salary 6000, August: 40 workday overtime hours claimed (base 50), everything else 0 Paid for 36 hours only: 50×36×1.5=2700.0, total 2700.0; note contains “超出法定上限 36 小时,超出部分按 4.0 小时核减”
shouldHandlePerformanceThresholdAtSixty E006/E007, base salary 9000, September: 10 years of service, 10 night shifts, 21 meal days, performance 59 vs 60 Score 59: performance bonus 0, total 1580.0; score 60 at the threshold gets 5% (450.0), total 2030.0; both share seniority 450, night shift 500, meal 630
  • Eight criteria externalized in XML, policy adjustments without releases: seniority 8%/5%/3%/1%, night shift 50 yuan, high temperature 30/15 yuan, overtime 1.5/2/3x, meal 30 yuan, performance 30%/20%/10%/5% all live in salary-allowance.xml; regional or annual standard changes only require editing the XML and rebuilding the proxy — month-end closing no longer depends on release windows.
  • One method per allowance, independently tunable and auditable: each allowance is a standalone rule method, so every item amount on the payslip traces back to its method and band, giving HR reviews and employee inquiries a clear basis.
  • Compliance gates up front on the Java side: the June-September payment-window check for the high-temperature allowance zeroes the item with an anti-mispayment note, and the statutory 36-hour monthly overtime cap is deducted before the script is called — no claim data can bypass hard compliance requirements.
  • BigDecimal two-decimal rounding throughout: scripts do pure numeric calculation; the Service normalizes every item, the total, and gross pay with BigDecimal HALF_UP, eliminating cent-level tail differences from chained double addition.
  • Enum-like overtime type injected by name: @Param("type") passes WORKDAY/WEEKEND/HOLIDAY into the script by name; multiplier branches are written positively with a workday-1.5x fallback, so no negation logic is needed in the script.
  • ASM dynamic proxy, Java 8, no heavyweight dependencies: the proxy is singleton-reusable with no separate service or extra middleware, embedding directly into legacy e-HR/payroll systems running on JDK 8 and IT-innovation JDKs.