案例 39:物业费/停车费减免资格判定
本案例对应 USE-CASES.md 场景 39(诉求 49:物业费/停车费减免资格判定),属于物业社区领域。规则脚本负责一票否决原因码、两类费用的减免比例与保障面积口径;Service 负责否决短路、保障面积封顶、减免金额计算与车位数限制,输出每户的资格结论、减免比例、计入面积、分项金额与审核说明。
业主减免申请由 PropertyWaiverApplication 承载:户号与业主信息之外,核心是家庭类型 householdType——SUBSISTENCE 低保特困、MARTYR 烈属优抚、DISABLED 重度残疾、LOW_INCOME 低保边缘、ELDERLY_ALONE 独居老人、ORDINARY 普通业主;房屋数据包括产权面积、家庭常住人口、人均建筑面积、物业费单价、申请减免月数;停车数据包括登记车位数与单个车位月费;合规标志包括房屋是否空置、是否出租、是否违法建设、历史欠费月数、是否存在电动车飞线充电/进梯入户等安全违规。
资格判定首先是四类一票否决:出租户(减免仅限自住)、违法建设、历史欠费超过 6 个月、存在停车安全违规,命中任一项直接返回原因码(RENTING / ILLEGAL / OVERDUE / SAFETY),不进入减免核算。通过否决后按家庭类型核定比例:低保特困、烈属、重残户物业费与停车费均 100% 减免;低保边缘物业 60%、停车 50%;独居老人物业 50%;普通业主空置房物业按 70% 收取(即减免 30%)、停车不免。
保障面积规则防止「大房套高比例」:低保特困、烈属、重残、独居老人按人均 30 ㎡保障线、低保边缘按人均 20 ㎡,人均建筑面积未超线按整套计入,超线则按「保障线 × 家庭人口」封顶,超出部分不免;普通空置房不适用保障线、按整套减免。停车费减免严格限定 1 个登记车位,多车位家庭其余车位正常缴纳。决策结果 PropertyWaiverDecision 输出资格结论、否决原因码、计入减免面积、两项减免比例与金额、减免合计及审核说明。
- 民生政策口径刚性强:减免比例与保障面积标准来自政府文件,出租、违建、欠费、安全违规必须一票否决,人工核算稍有放松就是补贴流失,过严又会引发投诉。
- 物业费与停车费是两套比例:同一家庭类型两项费用比例不同(如低保边缘 60% 对 50%),且停车减免只限 1 个车位,合并计算极易出错。
- 保障面积封顶计算复杂:要先比较人均面积是否超保障线,再按家庭人口算封顶面积,取产权面积与封顶面积的较小值作为计费基数。
- 空置房政策容易被滥用:普通业主空置房只享 30% 物业减免且停车不免,不能与困难群体的 100% 减免混淆。
- 金额精度与留痕要求高:减免金额涉及政府补贴与业主切身利益,必须精确到分,并生成「超出多少平方米不免」「其余车位正常缴纳」等可向业主解释的说明。
- 政策标准区域差异大、调整频繁:各地保障面积线(30/20 ㎡)与减免比例不同,硬编码进收费系统后跨项目复制困难。
| 组成 | 文件 | 职责 |
|---|---|---|
| 入参模型 | model/PropertyWaiverApplication.java |
家庭类型、房屋与车位数据、单价月数及四类合规标志 |
| 决策模型 | model/PropertyWaiverDecision.java |
资格结论、否决原因码、计入面积、两项比例与金额、合计、说明 |
| 规则接口 | PropertyWaiverRules.java |
4 个规则方法:否决原因、物业比例、停车比例、保障面积 |
| 规则脚本 | resources/demo/property-waiver.xml |
否决条件、家庭类型比例表、人均保障线全部外置 |
| 编排服务 | service/PropertyWaiverService.java |
否决短路、面积封顶选择、金额计算、车位限制与说明组装 |
| 测试 | PropertyWaiverServiceTest.java |
6 组家庭形态,含四类否决循环对照 |
规则方法拆分
Section titled “规则方法拆分”| 规则方法 | 入参(@Param) |
规则口径 | 返回 |
|---|---|---|---|
hardRejectReason |
renting、illegal、overdueMonths、safetyViolation |
出租返回 RENTING;违建 ILLEGAL;欠费超 6 个月 OVERDUE;安全违规 SAFETY;其余 PASS |
String 原因码 |
propertyWaiverPercent |
householdType、vacant |
低保特困/烈属/重残 100%;低保边缘 60%;独居老人 50%;普通空置房 30%;其余 0 | int 百分比 |
parkingWaiverPercent |
householdType |
低保特困/烈属/重残 100%(限 1 个车位);低保边缘 50%;其余 0 | int 百分比 |
qualifiedArea |
size、perCapita、family、householdType |
保障线:特困/烈属/重残/独居老人人均 30 ㎡、低保边缘人均 20 ㎡(默认 15 ㎡);人均未超线按整套,超线按保障线 × 家庭人口封顶,取较小值 | double 计入面积 |
将下列文件按包结构放入 JQuick-Java 测试工程:两个模型放入 src/test/java/com/github/paohaijiao/demo/model/,规则接口与测试放入 src/test/java/com/github/paohaijiao/demo/,Service 放入 demo/service/,XML 放入 src/test/resources/demo/。放入后执行:
mvn test -Dtest=PropertyWaiverServiceTestPropertyWaiverApplication.java
Section titled “PropertyWaiverApplication.java”package com.github.paohaijiao.demo.model;
import lombok.AllArgsConstructor;import lombok.Data;import lombok.NoArgsConstructor;
/** * 业主物业费/停车费减免申请要素 */@Data@NoArgsConstructor@AllArgsConstructorpublic class PropertyWaiverApplication {
/** * 户号 */ private String householdId;
private String ownerName;
/** * 家庭类型:SUBSISTENCE 低保特困 / MARTYR 烈属优抚 / DISABLED 重度残疾 / * LOW_INCOME 低保边缘 / ELDERLY_ALONE 独居老人 / ORDINARY 普通业主 */ private String householdType;
/** * 产权面积(㎡) */ private double propertySize;
/** * 家庭常住人口数 */ private int familySize;
/** * 人均建筑面积(㎡) */ private double perCapitaArea;
/** * 物业费单价(元/㎡/月) */ private double monthlyFeePerSqm;
/** * 申请减免月数 */ private int monthsApplied;
/** * 登记车位数 */ private int parkingSpaceCount;
/** * 单个车位月停车费 */ private double monthlyParkingFee;
/** * 房屋是否空置(未入住毛坯/长期空置,普通业主按 70% 缴纳) */ private boolean vacant;
/** * 是否出租(减免仅限自住) */ private boolean rentingOut;
/** * 是否存在违法建设 */ private boolean illegalConstruction;
/** * 历史欠费月数 */ private int overdueMonths;
/** * 是否存在电动车飞线充电/进梯入户等安全违规 */ private boolean parkingSafetyViolation;}PropertyWaiverDecision.java
Section titled “PropertyWaiverDecision.java”package com.github.paohaijiao.demo.model;
import lombok.Data;
import java.util.ArrayList;import java.util.List;
/** * 物业/停车费减免决策 */@Datapublic class PropertyWaiverDecision {
/** * 户号 */ private String householdId;
/** * 是否通过资格审核 */ private boolean eligible;
/** * 否决原因码:RENTING 出租 / ILLEGAL 违建 / OVERDUE 长期欠费 / SAFETY 安全违规 */ private String rejectReason;
/** * 计入减免的建筑面积(㎡,超出保障面积标准部分不免) */ private double qualifiedArea;
/** * 物业费减免比例(百分比) */ private int propertyWaiverPercent;
/** * 停车费减免比例(百分比) */ private int parkingWaiverPercent;
/** * 物业费减免金额 */ private double propertyWaiverAmount;
/** * 停车费减免金额 */ private double parkingWaiverAmount;
/** * 减免合计 */ private double totalWaiver;
/** * 审核说明 */ private List<String> notes = new ArrayList<String>();
public void addNote(String note) { this.notes.add(note); }}PropertyWaiverRules.java
Section titled “PropertyWaiverRules.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.xml.param.Param;
/** * 场景 39:物业/停车费减免资格、比例与保障面积规则 */public interface PropertyWaiverRules {
/** * 一票否决原因码:出租/违建/欠费超 6 个月/安全违规,其余 PASS */ String hardRejectReason(@Param("renting") boolean renting, @Param("illegal") boolean illegal, @Param("overdueMonths") int overdueMonths, @Param("safetyViolation") boolean safetyViolation);
/** * 物业费减免比例:低保特困/烈属/重残 100%、边缘困难 60%、独居老人 50%、 * 普通空置房 30%,其余 0 */ int propertyWaiverPercent(@Param("householdType") String householdType, @Param("vacant") boolean vacant);
/** * 停车费减免比例:低保特困/烈属/重残 100%(限 1 个车位)、边缘困难 50%,其余 0 */ int parkingWaiverPercent(@Param("householdType") String householdType);
/** * 计入减免的面积:低保特困/烈属/重残/独居老人按人均 30 ㎡保障线、 * 边缘困难按人均 20 ㎡,超出部分按家庭人口封顶;未超标按整套 */ double qualifiedArea(@Param("size") double size, @Param("perCapita") double perCapita, @Param("family") int family, @Param("householdType") String householdType);}property-waiver.xml
Section titled “property-waiver.xml”<?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.PropertyWaiverRules">
<java name="hardRejectReason" returnClass="java.lang.String"> <![CDATA[ str def hardRejectReason(boolean:renting, boolean:illegal, int:overdueMonths, boolean:safetyViolation) { if (renting) { return "RENTING"; } else if (illegal) { return "ILLEGAL"; } else if (overdueMonths > 6) { return "OVERDUE"; } else if (safetyViolation) { return "SAFETY"; } else { return "PASS"; } } ]]> </java>
<java name="propertyWaiverPercent" returnClass="int"> <![CDATA[ int def propertyWaiverPercent(str:householdType, boolean:vacant) { if (householdType == "SUBSISTENCE") { return 100; } else if (householdType == "MARTYR") { return 100; } else if (householdType == "DISABLED") { return 100; } else if (householdType == "LOW_INCOME") { return 60; } else if (householdType == "ELDERLY_ALONE") { return 50; } else if (vacant) { return 30; } else { return 0; } } ]]> </java>
<java name="parkingWaiverPercent" returnClass="int"> <![CDATA[ int def parkingWaiverPercent(str:householdType) { if (householdType == "SUBSISTENCE") { return 100; } else if (householdType == "MARTYR") { return 100; } else if (householdType == "DISABLED") { return 100; } else if (householdType == "LOW_INCOME") { return 50; } else { return 0; } } ]]> </java>
<java name="qualifiedArea" returnClass="double"> <![CDATA[ double def qualifiedArea(double:size, double:perCapita, int:family, str:householdType) { double cap = 15.0; if (householdType == "SUBSISTENCE") { cap = 30.0; } else if (householdType == "MARTYR") { cap = 30.0; } else if (householdType == "DISABLED") { cap = 30.0; } else if (householdType == "ELDERLY_ALONE") { cap = 30.0; } else if (householdType == "LOW_INCOME") { cap = 20.0; } if (perCapita <= cap) { return size; } double limit = cap * family; if (size < limit) { return size; } else { return limit; } } ]]> </java>
</javas>PropertyWaiverService.java
Section titled “PropertyWaiverService.java”package com.github.paohaijiao.demo.service;
import com.github.paohaijiao.demo.PropertyWaiverRules;import com.github.paohaijiao.demo.model.PropertyWaiverApplication;import com.github.paohaijiao.demo.model.PropertyWaiverDecision;
import java.math.BigDecimal;import java.math.RoundingMode;
/** * 场景 39:物业费/停车费减免资格判定 */public class PropertyWaiverService {
private final PropertyWaiverRules rules;
public PropertyWaiverService(PropertyWaiverRules rules) { this.rules = rules; }
public PropertyWaiverDecision review(PropertyWaiverApplication a) { PropertyWaiverDecision d = new PropertyWaiverDecision(); d.setHouseholdId(a.getHouseholdId());
String reject = rules.hardRejectReason(a.isRentingOut(), a.isIllegalConstruction(), a.getOverdueMonths(), a.isParkingSafetyViolation()); if (!"PASS".equals(reject)) { d.setEligible(false); d.setRejectReason(reject); d.setQualifiedArea(0.0); return d; } d.setEligible(true);
int propertyPercent = rules.propertyWaiverPercent(a.getHouseholdType(), a.isVacant()); int parkingPercent = rules.parkingWaiverPercent(a.getHouseholdType()); d.setPropertyWaiverPercent(propertyPercent); d.setParkingWaiverPercent(parkingPercent);
double area; if ("ORDINARY".equals(a.getHouseholdType()) && a.isVacant()) { area = a.getPropertySize(); } else { area = rules.qualifiedArea(a.getPropertySize(), a.getPerCapitaArea(), a.getFamilySize(), a.getHouseholdType()); } d.setQualifiedArea(money(area)); if (area < a.getPropertySize()) { d.addNote("人均建筑面积超出保障标准,超出的 " + money(a.getPropertySize() - area) + " ㎡ 部分不予减免"); }
double propertyAmount = money(area * a.getMonthlyFeePerSqm() * a.getMonthsApplied() * propertyPercent / 100.0); d.setPropertyWaiverAmount(propertyAmount);
double parkingAmount = 0.0; if (a.getParkingSpaceCount() >= 1 && parkingPercent > 0) { parkingAmount = money(a.getMonthlyParkingFee() * a.getMonthsApplied() * parkingPercent / 100.0); if (a.getParkingSpaceCount() > 1) { d.addNote("停车费减免仅限 1 个登记车位,其余 " + (a.getParkingSpaceCount() - 1) + " 个车位正常缴纳"); } } d.setParkingWaiverAmount(parkingAmount); d.setTotalWaiver(money(propertyAmount + parkingAmount));
if ("ORDINARY".equals(a.getHouseholdType()) && a.isVacant()) { d.addNote("空置房物业费按应缴标准的 70% 收取(减免 30%),停车费不免"); } return d; }
private double money(double v) { return BigDecimal.valueOf(v).setScale(2, RoundingMode.HALF_UP).doubleValue(); }}PropertyWaiverServiceTest.java
Section titled “PropertyWaiverServiceTest.java”package com.github.paohaijiao.demo;
import com.github.paohaijiao.demo.model.PropertyWaiverApplication;import com.github.paohaijiao.demo.model.PropertyWaiverDecision;import com.github.paohaijiao.demo.service.PropertyWaiverService;import com.github.paohaijiao.xml.JQuickJava;import org.junit.Assert;import org.junit.Before;import org.junit.Test;
/** * 场景 39:物业费/停车费减免资格判定 * <p> * 6 组形态:低保户物业停车全免、烈属全免无车位、低保边缘比例减免与车位限制、 * 空置房 70% 缴纳、四类一票否决、独居老人超保障面积封顶。 */public class PropertyWaiverServiceTest {
private PropertyWaiverService service;
@Before public void setUp() { PropertyWaiverRules rules = JQuickJava.create() .createApi(PropertyWaiverRules.class, "demo/property-waiver.xml"); this.service = new PropertyWaiverService(rules); }
/** * 低保户 80 ㎡ 四口之家、6 个月、1 车位 → 物业停车全免 3000 */ @Test public void shouldFullyWaiveForSubsistenceHousehold() { PropertyWaiverApplication a = base("H001", "SUBSISTENCE"); a.setPropertySize(80); a.setFamilySize(4); a.setPerCapitaArea(20); a.setMonthlyFeePerSqm(2.5); a.setMonthsApplied(6); a.setParkingSpaceCount(1); a.setMonthlyParkingFee(300);
PropertyWaiverDecision d = service.review(a);
Assert.assertTrue(d.isEligible()); Assert.assertEquals(80.0, d.getQualifiedArea(), 0.0001); Assert.assertEquals(100, d.getPropertyWaiverPercent()); Assert.assertEquals(100, d.getParkingWaiverPercent()); Assert.assertEquals(1200.0, d.getPropertyWaiverAmount(), 0.0001); Assert.assertEquals(1800.0, d.getParkingWaiverAmount(), 0.0001); Assert.assertEquals(3000.0, d.getTotalWaiver(), 0.0001); }
/** * 烈属 90 ㎡ 人均 30 ㎡ 不超标、全年、无车位 → 全免 2160 */ @Test public void shouldFullyWaivePropertyForMartyrWithoutParking() { PropertyWaiverApplication a = base("H002", "MARTYR"); a.setPropertySize(90); a.setFamilySize(3); a.setPerCapitaArea(30); a.setMonthlyFeePerSqm(2); a.setMonthsApplied(12); a.setParkingSpaceCount(0); a.setMonthlyParkingFee(0);
PropertyWaiverDecision d = service.review(a);
Assert.assertTrue(d.isEligible()); Assert.assertEquals(90.0, d.getQualifiedArea(), 0.0001); Assert.assertEquals(2160.0, d.getPropertyWaiverAmount(), 0.0001); Assert.assertEquals(0.0, d.getParkingWaiverAmount(), 0.0001); Assert.assertEquals(2160.0, d.getTotalWaiver(), 0.0001); }
/** * 低保边缘 70 ㎡ 不超标、2 车位 → 物业 60%、1 个车位停车 50% */ @Test public void shouldPartiallyWaiveForLowIncomeAndLimitOneParkingSpace() { PropertyWaiverApplication a = base("H003", "LOW_INCOME"); a.setPropertySize(70); a.setFamilySize(4); a.setPerCapitaArea(17.5); a.setMonthlyFeePerSqm(2); a.setMonthsApplied(12); a.setParkingSpaceCount(2); a.setMonthlyParkingFee(200);
PropertyWaiverDecision d = service.review(a);
Assert.assertEquals(60, d.getPropertyWaiverPercent()); Assert.assertEquals(70.0, d.getQualifiedArea(), 0.0001); Assert.assertEquals(1008.0, d.getPropertyWaiverAmount(), 0.0001); Assert.assertEquals(1200.0, d.getParkingWaiverAmount(), 0.0001); Assert.assertEquals(2208.0, d.getTotalWaiver(), 0.0001); Assert.assertTrue(join(d).contains("停车费减免仅限 1 个登记车位")); }
/** * 普通业主 100 ㎡ 空置 6 个月 → 按整套减免 30%,停车不免 */ @Test public void shouldWaiveThirtyPercentForVacantApartment() { PropertyWaiverApplication a = base("H004", "ORDINARY"); a.setPropertySize(100); a.setFamilySize(3); a.setPerCapitaArea(33.33); a.setMonthlyFeePerSqm(2); a.setMonthsApplied(6); a.setParkingSpaceCount(1); a.setMonthlyParkingFee(200); a.setVacant(true);
PropertyWaiverDecision d = service.review(a);
Assert.assertTrue(d.isEligible()); Assert.assertEquals(100.0, d.getQualifiedArea(), 0.0001); Assert.assertEquals(30, d.getPropertyWaiverPercent()); Assert.assertEquals(360.0, d.getPropertyWaiverAmount(), 0.0001); Assert.assertEquals(0.0, d.getParkingWaiverAmount(), 0.0001); Assert.assertTrue(join(d).contains("空置房物业费按应缴标准的 70% 收取")); }
/** * 出租、违建、欠费 8 个月、飞线充电 → 四类一票否决 */ @Test public void shouldHardRejectFourVetoConditions() { String[][] cases = { {"LOW_INCOME", "RENTING"}, {"SUBSISTENCE", "ILLEGAL"}, {"MARTYR", "OVERDUE"}, {"DISABLED", "SAFETY"} }; for (String[] c : cases) { PropertyWaiverApplication a = base("H005", c[0]); a.setPropertySize(60); a.setFamilySize(2); a.setPerCapitaArea(30); a.setMonthlyFeePerSqm(2); a.setMonthsApplied(12); if ("RENTING".equals(c[1])) { a.setRentingOut(true); } else if ("ILLEGAL".equals(c[1])) { a.setIllegalConstruction(true); } else if ("OVERDUE".equals(c[1])) { a.setOverdueMonths(8); } else { a.setParkingSafetyViolation(true); } PropertyWaiverDecision d = service.review(a); Assert.assertFalse(c[0] + " 应被否决", d.isEligible()); Assert.assertEquals(c[0] + " 否决原因码不符", c[1], d.getRejectReason()); Assert.assertEquals(0.0, d.getTotalWaiver(), 0.0001); } }
/** * 独居老人 60 ㎡(人均超 30 ㎡保障线)→ 仅 30 ㎡享 50% 减免 */ @Test public void shouldCapElderlyWaiverAtSecurityAreaStandard() { PropertyWaiverApplication a = base("H006", "ELDERLY_ALONE"); a.setPropertySize(60); a.setFamilySize(1); a.setPerCapitaArea(60); a.setMonthlyFeePerSqm(2); a.setMonthsApplied(12); a.setParkingSpaceCount(0);
PropertyWaiverDecision d = service.review(a);
Assert.assertEquals(50, d.getPropertyWaiverPercent()); Assert.assertEquals(30.0, d.getQualifiedArea(), 0.0001); Assert.assertEquals(360.0, d.getPropertyWaiverAmount(), 0.0001); Assert.assertTrue(join(d).contains("超出的 30.0 ㎡ 部分不予减免")); }
private PropertyWaiverApplication base(String id, String type) { PropertyWaiverApplication a = new PropertyWaiverApplication(); a.setHouseholdId(id); a.setOwnerName("测试业主"); a.setHouseholdType(type); a.setPropertySize(0); a.setFamilySize(1); a.setPerCapitaArea(0); a.setMonthlyFeePerSqm(0); a.setMonthsApplied(0); a.setParkingSpaceCount(0); a.setMonthlyParkingFee(0); a.setVacant(false); a.setRentingOut(false); a.setIllegalConstruction(false); a.setOverdueMonths(0); a.setParkingSafetyViolation(false); return a; }
private String join(PropertyWaiverDecision d) { StringBuilder sb = new StringBuilder(); for (String n : d.getNotes()) { sb.append(n).append(';'); } return sb.toString(); }}在工程根目录执行:
mvn test -Dtest=PropertyWaiverServiceTest测试覆盖 6 组家庭形态(一票否决用例在一个测试方法内循环 4 类家庭),断言与源码逐一对应:
| 测试方法 | 业务形态 | 关键断言 |
|---|---|---|
shouldFullyWaiveForSubsistenceHousehold |
低保户 80 ㎡四口之家(人均 20 未超线)、6 个月、1 车位 | 合格;计入面积 80;物业/停车均 100%;物业减免 1200(80×2.5×6)、停车 1800(300×6)、合计 3000 |
shouldFullyWaivePropertyForMartyrWithoutParking |
烈属 90 ㎡人均 30 恰不超线、全年、无车位 | 计入面积 90;物业减免 2160(90×2×12);停车 0;合计 2160 |
shouldPartiallyWaiveForLowIncomeAndLimitOneParkingSpace |
低保边缘 70 ㎡(人均 17.5 未超 20 线)、2 车位 | 物业 60%、计入 70 ㎡、减免 1008;停车按 1 车位 50% 减免 1200;合计 2208;说明含「停车费减免仅限 1 个登记车位」 |
shouldWaiveThirtyPercentForVacantApartment |
普通业主 100 ㎡空置 6 个月、1 车位 | 计入整套 100 ㎡;物业 30% 减免 360(100×2×6×30%);停车 0;说明含「空置房物业费按应缴标准的 70% 收取」 |
shouldHardRejectFourVetoConditions |
边缘户出租、特困户违建、烈属欠费 8 个月、重残户飞线充电 | 四类均不合格;否决原因码分别为 RENTING/ILLEGAL/OVERDUE/SAFETY;减免合计均为 0 |
shouldCapElderlyWaiverAtSecurityAreaStandard |
独居老人 60 ㎡单人(人均 60 超 30 ㎡保障线) | 物业 50%;计入面积封顶 30 ㎡;减免 360(30×2×12×50%);说明含「超出的 30.0 ㎡ 部分不予减免」 |
- 一票否决前置短路:
hardRejectReason返回原因码后 Service 立即结束流程,计入面积与金额全部归零,否决优先级(出租 → 违建 → 欠费 → 安全)在脚本中以正向条件顺序表达,清晰可审计。 - 一方法一口径:否决原因、物业比例、停车比例、保障面积四个规则方法完全独立,物业与停车两套比例表分开维护,调整某一项不会误伤另一项。
- Service 封顶与定额兜底:保障面积「未超线按整套、超线按保障线 × 人口封顶」的取小逻辑由规则给面积、Service 负责与产权面积比较并生成差额说明;空置房走按整套特例;停车减免在 Java 侧限定 1 车位。
- 金额精确到分:所有金额经
BigDecimal+HALF_UP保留两位小数,物业、停车、合计三处统一走money()口径,民生补贴金额无浮点尾差。 - 规则外置热更新:家庭类型比例表(100/60/50/30)、欠费 6 个月亮灯线、人均 30/20 ㎡保障线全部在 XML 中,跨城市接项目时替换阈值表即可复用整套骨架。
- 代理复用、ASM、Java 8 信创友好:代理在
@Before中创建一次、6 组形态共用;引擎基于动态代理与 ASM、无重型依赖,可嵌入 JDK 8 与国产化 JDK 的物业收费系统。