feat:独生子女年度清算应发、实发计算逻辑修复

This commit is contained in:
2026-11-13 11:21:14 +08:00
parent e788899fcd
commit beaf349136
2 changed files with 8 additions and 26 deletions
@@ -403,7 +403,7 @@ public class OnlyChildFeeService {
/** /**
* 保存领取人首次实际发放月份,各年度共用,并重算未确认草稿。 * 保存领取人首次实际发放月份,各年度共用,并重算未确认草稿。
* @param d 领取人、当前办理年度和首次实际发放月份 * @param d 领取人、当前办理年度和首次实际发放月份
* 起发前及停发当月起为零,发放期间每月10元;单月人工修订优先,已确认批次禁止修改。 * 起发前为零、起发后每月10元,不按待遇停发月自动清零实发;单月人工修订优先,已确认批次禁止修改。
*/ */
public void paymentStart(JSONObject d) { public void paymentStart(JSONObject d) {
access("settlement"); lock(); access("settlement"); lock();
@@ -444,32 +444,14 @@ public class OnlyChildFeeService {
// 先读取单月修订记录,再按首次发放月份计算默认实发。 // 先读取单月修订记录,再按首次发放月份计算默认实发。
JSONObject actual = get("M:" + user + ":" + period); JSONObject actual = get("M:" + user + ":" + period);
if (actual != null && !Boolean.TRUE.equals(actual.getBool("automatic"))) return actual; if (actual != null && !Boolean.TRUE.equals(actual.getBool("automatic"))) return actual;
// 到龄或资格终止当月自动停发;真实多发由上方的单月修订记录保留。
if (automaticPaymentStopped(q, month(period))) {
return new JSONObject().set("userId", user).set("month", period).set("automatic", true)
.set("paid", BigDecimal.ZERO).set("source", "按停发月份自动停发");
}
if (baseline == null) return actual; if (baseline == null) return actual;
// 各年度共用首次发放月份,仅在尚未停发的期间自动计发。 // 待遇停发只影响应发,不能据此认定工资已停发;保留实发差额供管理员登记扣回。
YearMonth start = month(baseline.getStr("actualStartMonth")); YearMonth start = month(baseline.getStr("actualStartMonth"));
return new JSONObject().set("userId", user).set("month", period).set("automatic", true) return new JSONObject().set("userId", user).set("month", period).set("automatic", true)
.set("paid", month(period).isBefore(start) ? BigDecimal.ZERO : TEN) .set("paid", month(period).isBefore(start) ? BigDecimal.ZERO : TEN)
.set("source", "按首次实际发放月份" + start + "自动计算"); .set("source", "按首次实际发放月份" + start + "自动计算");
} }
private boolean automaticPaymentStopped(JSONObject q, YearMonth period) {
// 资料更正按生效月份分段,不能用更正后的停发月倒改此前的默认实发。
JSONArray history = q.getJSONArray("benefitHistory");
if (history != null) {
for (int i = 0; i < history.size(); i++) {
JSONObject version = history.getJSONObject(i);
if (period.isBefore(month(version.getStr("untilMonth")))) return automaticPaymentStopped(version, period);
}
}
return Boolean.FALSE.equals(q.getBool("eligible")) || !period.isBefore(month(q.getStr("stopMonth")))
|| (q.getStr("endMonth") != null && !period.isBefore(month(q.getStr("endMonth"))));
}
// 11、12月归属下一年度清算,每批覆盖上一年11月至当年10月。 // 11、12月归属下一年度清算,每批覆盖上一年11月至当年10月。
private int settlementYear(YearMonth period) { private int settlementYear(YearMonth period) {
return period.getMonthValue() >= 11 ? period.getYear() + 1 : period.getYear(); return period.getMonthValue() >= 11 ? period.getYear() + 1 : period.getYear();
@@ -478,7 +460,7 @@ public class OnlyChildFeeService {
private void refreshDraft(JSONObject draft) { private void refreshDraft(JSONObject draft) {
int year = draft.getInt("year"); int year = draft.getInt("year");
draft.set("rows", calculate(year)).set("periodStart", YearMonth.of(year - 1, 11).toString()) draft.set("rows", calculate(year)).set("periodStart", YearMonth.of(year - 1, 11).toString())
.set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", true).set("qualificationStartSynced", true); .set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", false).set("qualificationStartSynced", true);
put("B:" + year, "BATCH", null, String.valueOf(year), draft); put("B:" + year, "BATCH", null, String.valueOf(year), draft);
} }
@@ -517,7 +499,7 @@ public class OnlyChildFeeService {
.set("source", actual == null ? null : actual.getStr("source")).set("recovered", deducted).set("pending", remaining) .set("source", actual == null ? null : actual.getStr("source")).set("recovered", deducted).set("pending", remaining)
.set("note", m.equals(q.getStr("stopMonth")) ? "满18周岁当月停发" : "")); .set("note", m.equals(q.getStr("stopMonth")) ? "满18周岁当月停发" : ""));
} }
// 已在往期停发且本期无应发、真实实发或扣回记录的人员,不重复进入年度清算。 // 往期已停发但本期仍有实发的人员继续清算,仅排除本期应发、实发及扣回均为零的人员。
if (end.compareTo(periodStart.toString()) < 0 && expected.signum() == 0 && paid.signum() == 0 && back.signum() == 0) continue; if (end.compareTo(periodStart.toString()) < 0 && expected.signum() == 0 && paid.signum() == 0 && back.signum() == 0) continue;
rows.add(new JSONObject(q.toString()).set("actualStartMonth", baseline == null ? null : baseline.getStr("actualStartMonth")) rows.add(new JSONObject(q.toString()).set("actualStartMonth", baseline == null ? null : baseline.getStr("actualStartMonth"))
.set("months", months).set("due", expected).set("paid", complete ? paid : null) .set("months", months).set("due", expected).set("paid", complete ? paid : null)
@@ -535,10 +517,10 @@ public class OnlyChildFeeService {
access("settlement", "changes"); lock(); access("settlement", "changes"); lock();
require(year >= 1900 && year <= 9999, "年度无效"); require(year >= 1900 && year <= 9999, "年度无效");
JSONObject b = get("B:" + year); JSONObject b = get("B:" + year);
// 自动停发及起发月同步只重算未确认草稿,已确认批次的金额及期间保持冻结。 // 重算曾按待遇停发月清零实发的未确认草稿;已确认批次的金额及期间保持冻结。
if (b != null && "草稿".equals(b.getStr("status")) if (b != null && "草稿".equals(b.getStr("status"))
&& (!YearMonth.of(year - 1, 11).toString().equals(b.getStr("periodStart")) && (!YearMonth.of(year - 1, 11).toString().equals(b.getStr("periodStart"))
|| !Boolean.TRUE.equals(b.getBool("automaticStop")) || !Boolean.FALSE.equals(b.getBool("automaticStop"))
|| !Boolean.TRUE.equals(b.getBool("qualificationStartSynced")))) refreshDraft(b); || !Boolean.TRUE.equals(b.getBool("qualificationStartSynced")))) refreshDraft(b);
if (b != null && b.getStr("periodStart") == null) { if (b != null && b.getStr("periodStart") == null) {
b.set("periodStart", YearMonth.of(year, 1).toString()).set("periodEnd", YearMonth.of(year, 10).toString()); b.set("periodStart", YearMonth.of(year, 1).toString()).set("periodEnd", YearMonth.of(year, 10).toString());
@@ -564,7 +546,7 @@ public class OnlyChildFeeService {
boolean pending = false; boolean pending = false;
for (int i = 0; i < rows.size(); i++) pending |= rows.getJSONObject(i).getBigDecimal("pending", BigDecimal.ZERO).signum() > 0; for (int i = 0; i < rows.size(); i++) pending |= rows.getJSONObject(i).getBigDecimal("pending", BigDecimal.ZERO).signum() > 0;
JSONObject b = new JSONObject().set("year", year).set("id", year + "年11月").set("rows", rows) JSONObject b = new JSONObject().set("year", year).set("id", year + "年11月").set("rows", rows)
.set("periodStart", YearMonth.of(year - 1, 11).toString()).set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", true).set("qualificationStartSynced", true) .set("periodStart", YearMonth.of(year - 1, 11).toString()).set("periodEnd", YearMonth.of(year, 10).toString()).set("automaticStop", false).set("qualificationStartSynced", true)
.set("status", confirm ? (pending ? "已确认" : "已完成") : "草稿") .set("status", confirm ? (pending ? "已确认" : "已完成") : "草稿")
.set("operator", SecurityUtil.getUserId()).set("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); .set("operator", SecurityUtil.getUserId()).set("time", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
put("B:" + year, "BATCH", null, String.valueOf(year), b); put("B:" + year, "BATCH", null, String.valueOf(year), b);
@@ -89,7 +89,7 @@ new Vue({
row.actualStartMonth=previous; row.actualStartMonth=previous;
if(!selected||selected===previous)return; if(!selected||selected===previous)return;
this.perform(async()=>{ this.perform(async()=>{
try{await this.$confirm('将首次实际发放月份设为 '+selected+'。更改后,各年度未确认的实发记录会重新计算,停发当月起默认实发为0;单月修订和已确认快照保留。是否确认?','确认起发月份',{type:'warning',confirmButtonText:'确认保存',cancelButtonText:'取消'});} try{await this.$confirm('将首次实际发放月份设为 '+selected+'。更改后,各年度未确认的实发记录按起发前0元、起发后每月10元重新计算,待遇停发月份只影响应发;单月修订和已确认快照保留。是否确认?','确认起发月份',{type:'warning',confirmButtonText:'确认保存',cancelButtonText:'取消'});}
catch(e){return;} catch(e){return;}
await this.request('paymentStart',{data:JSON.stringify({userId:row.userId,year:Number(this.year),actualStartMonth:selected})}); await this.request('paymentStart',{data:JSON.stringify({userId:row.userId,year:Number(this.year),actualStartMonth:selected})});
await this.fetchSettlement();this.expandedRowKeys=[row.userId]; await this.fetchSettlement();this.expandedRowKeys=[row.userId];