Java养老系统源码详解:入住核算单 live-check 缴费入账与预交金收据实践
在线演示 :管理端后台
标签:#Java #Spring Boot #MyBatis #养老系统 #入住核算单 #care_bill_payment #changeBiz
1. 入住核算单与缴费入账
合同审批通过后 费用管理 · 入住核算单 出现一行 paymentType=入住;前台录入预交金、押金实缴,后端 CareBillPaymentServiceImpl.pay 调 CareAccountService.changeBiz,bizType=BILL_PAYMENT,并带 care_pay_way 交易方式。
开户在合同生效时 getOrOpen 已完成;核算单只负责应缴实缴对齐,不重复开户。

图1 入住核算单 · 押金/预交金/一次性分项

图2 缴费后账户流水
2. CareBillPaymentDO 与状态常量
private static final int TYPE_CHECKIN = 1;
private static final int STATUS_PENDING = 0;
private static final int STATUS_PARTIAL = 1;
private static final int STATUS_DONE = 2;
private static final int WALLET_BALANCE = 1;
private static final int WALLET_DEPOSIT = 2;
private static final int CHANGE_RECHARGE = 1;
@TableName("care_bill_payment")
public class CareBillPaymentDO extends CareOrgBaseDO {
private Long olderId;
private Long checkinId;
private Integer paymentType;
private BigDecimal depositDue;
private BigDecimal advanceDue;
private BigDecimal amountDue;
private BigDecimal advancePaid;
private BigDecimal depositPaid;
private Integer status;
}
3. generateCheckinFromApply(审批通过生成单据)
public Long generateCheckinFromApply(CareContractDO contract, CareContractSubmitCheckinReqVO reqVO) {
CareBillPaymentDO exists = careBillPaymentMapper.selectByCheckinIdAndType(
contract.getCheckinId(), TYPE_CHECKIN);
if (exists != null) {
return exists.getId();
}
payment.setPaymentType(TYPE_CHECKIN);
payment.setTitle("入住核算单");
payment.setDepositDue(depositDue);
payment.setAdvanceDue(advanceDue);
careBillPaymentMapper.insert(payment);
return payment.getId();
}
4. pay:预交金/押金分钱包 + 缴清扣一次性费
public List<Long> pay(CareBillPaymentPayReqVO reqVO) {
if (advance.compareTo(BigDecimal.ZERO) > 0) {
flowIds.add(careAccountService.changeBiz(
exists.getOlderId(), WALLET_BALANCE, CHANGE_RECHARGE,
advance, "a5", "BILL_PAYMENT", exists.getId(), payRemark, reqVO.getPayWay()));
}
if (deposit.compareTo(BigDecimal.ZERO) > 0) {
flowIds.add(careAccountService.changeBiz(
exists.getOlderId(), WALLET_DEPOSIT, CHANGE_RECHARGE,
deposit, "b1", "BILL_PAYMENT", exists.getId(), payRemark, reqVO.getPayWay()));
}
if (becomingDone && Objects.equals(exists.getPaymentType(), TYPE_CHECKIN)) {
flowIds.addAll(deductCheckinOneTimeFees(exists, reqVO.getPayWay()));
}
}
- advanceOk && depositOk:分项达标才 STATUS_DONE;被褥/其他一次性费在缴清后从预交金扣。
5. Controller 与 curl
@RestController
@RequestMapping("/care/bill-payment")
public class CareBillPaymentController {
@PostMapping("/pay")
public CommonResult<List<Long>> pay(@Valid @RequestBody CareBillPaymentPayReqVO reqVO) {
return success(careBillPaymentService.pay(reqVO));
}
}
curl -X POST "http://localhost:48080/admin-api/care/bill-payment/pay" \
-H "Authorization: Bearer {token}" \
-H "tenant-id: 123" \
-H "Content-Type: application/json" \
-d '{"id":6001,"advancePaid":5000,"depositPaid":2000,"payWay":"1"}'
补充:缴清顺序与一次性费用
pay 里要先 updateById 把核算单标为已缴清 ,再扣被褥等一次性费;否则 deductCheckinOneTimeFees 仍被「入住核算未缴清」拦住(社区版已按此顺序修复)。

图3 费用管理 · 入住核算单筛选

图4 合同生效后才会生成 TYPE_CHECKIN 核算单
// CareBillPaymentServiceImpl.pay(逻辑顺序)
careBillPaymentMapper.updateById(doneUpdate); // status=STATUS_DONE
if (becomingDone && Objects.equals(exists.getPaymentType(), TYPE_CHECKIN)) {
flowIds.addAll(deductCheckinOneTimeFees(exists, reqVO.getPayWay()));
deductCheckinMonthBillFullIfNeeded(exists, reqVO.getPayWay());
}
| 钱包 | changeType | 典型科目 |
|---|---|---|
| 预交金 | 充值 | a5 |
| 押金 | 充值 | b1 |
| 预交金 | 扣费 | a1/a2/a3 一次性 |
curl -G "http://localhost:48080/admin-api/care/bill-payment/page" -H "Authorization: Bearer {token}" -H "tenant-id: 123" --data-urlencode "paymentType=1" --data-urlencode "status=0"

图5 缴清后首月整单扣费依赖计费设置(见 30 篇)
6. 踩坑
- 重复 pay:STATUS_DONE 抛 BILL_PAYMENT_STATUS_ERROR。
- 只传 amount 老字段:整笔当预交金,新前端应拆 advancePaid/depositPaid。
- 缴清但未扣一次性费:检查 becomingDone 与 feeItems 是否入库。
- tenant-id:流水按租户隔离,联调必带。
补充:日志与排错顺序
-
浏览器 Network 看
/admin-api返回 code/msg。 -
IDEA 断点 Service 入口,确认 tenant-id 与业务 id。
-
查库 status 与界面一致(勿只改 checkin 不改 bed)。
-
定时任务看 infra_job 日志与 @TenantJob 租户上下文。
curl -X POST -H "tenant-id: 123" -H "Content-Type: application/json"
-d "{"phone":"13900001001","password":"123456","accountType":2}"
http://127.0.0.1:8086/app-api/care/family-auth/login
联调:application-local 与 tenant-id
本地 zhijian-server 联调时,管理端请求统一带头:
yudao:
tenant:
enable: true
spring:
profiles:
active: local
curl -H "Authorization: Bearer {token}" -H "tenant-id: 123" \
"http://localhost:48080/admin-api/care/..."
403 先查角色是否勾选菜单按钮权限;401 查 token 是否过期。多园环境 rootDeptId 与登录部门一致,否则计费/报表可能看到空列表。
模块路径(Maven)
| 模块 | 典型包路径 |
|---|---|
| institution | zhijian-module-institution/.../service/ |
| hub | zhijian-module-hub/.../service/hub/ |
| bi | zhijian-module-bi/.../service/bi/ |
| monitor | zhijian-module-monitor/.../ |
| community | zhijian-module-community/.../ |
| medical | zhijian-module-medical/.../ |
交付裁剪时 zhijian-server/pom.xml 注释未购业态 JAR,并执行 care_maven_delivery_menu_crop.sql 隐藏菜单,避免「有菜单无 API」。
与 hub 中台衔接
老人主档 care_older 在 hub;机构业务 care_checkin / care_contract 在 institution。中台档案页不存 bed_id,床态以 care_bed.status 为准(见 24 篇)。跨模块只通过 olderId、SPI Bridge 或 REST,不在 hub 里直接 join institution 大表。
演示站对照
在线演示:管理端后台 --- 登录后左侧菜单与源码 @PreAuthorize 权限码一致;改 SQL 菜单后需重新登录刷新路由。
Gitee 社区版源码 :https://gitee.com/xinzexingjian-data_0/older_care