跳转至

退房净额的单一事实来源:一个金额只能有一个爹

退房结算页上有好几个数字:应收、余额、超时费、净额、确认按钮上的合计。它们曾经各有一套来源,结果就是最该出现超时费的地方不出现、最该一致的估算和结算对不上。这篇文章记录我们把"净额"收敛到单一事实来源的过程——以及为什么坑不在计算公式,而在错误路径的排序。

现象

前台点开退房结算弹窗,配了超时规则、确实超时了的在住单,超时费和应收预览整体不出现;房态图悬浮卡上的即时估算,和结算页的数字偶尔对不上。症状极具迷惑性:看起来像"超时费功能没实现"。

现场记录(据当时的线上反馈与修复提交 ae8ddcc 的记载):超时规则上线次日,逾期房间的退房结算弹窗不出现超时费——而房价计划里规则配置得整整齐齐,小时价与宽限分钟都在。云端当时升级前入住的在住单共六间,规则快照全部是零。

我一开始的错误判断

排障顺序是三层递进的(按提交时间线回看:规则与草稿报价于 2026-10-27 上线,次日线上反馈弹窗不出现超时费,当天定位修复):

  1. 先怀疑超时规则没算出来——去查规则配置与计费入口,规则好端端的;
  2. 再怀疑前端没渲染——去查展示层,字段压根没到达;
  3. 最后才发现是两个原因叠加:试算请求在服务端被"未结清"校验拦成了错误,前端把错误静默吞掉,余额大于零的在住单每次打开弹窗都到不了试算那一步;同时升级前入住的存量单规则快照为零,旧口径把零当"不计超时"直接跳过报价。两个缺陷单看都不致命,叠加起来就是"功能整体隐身"。

排查与关键证据

关键证据就在服务端退房函数里:修复把试算的提前返回挪到了未结清校验之前,并在代码注释里写清了顺序因果——试算纯读不落账,未结清金额照常在结果里返回;落账路径仍由原校验拦截。注释同时点名了旧顺序的实际后果:余额与未入账之和大于零的在住单,打开结算弹窗即被拦截,前端静默,预览整体不显示。

配套的契约改造有三处:

  • 退房入参增加试算模式标记:只出报价不落账,确认退房是不带标记的第二次调用;返回/关闭无任何写入,超时费因此免冲红;
  • 试算结果专返试算时点余额快照,供前端实时推导净额——前端不再自己减,减法只存在一份;
  • 超时费支持人工权衡入参:按规则自动(小时进位加封顶)、免收、按操作员确认金额收——现场"超十分钟收一小时"这类定夺有了合法出口。

计价规则本身的回归测试覆盖了宽限、进位、封顶、转全天、跨天、无单价等分支(overtime_quote_test.go 的六个用例),试算与夜审共用同一套取价口径——这些断言全部可重复执行(本仓库 go test ./internal/mod/frontdesk/ 实测通过),修复提交的回归记录里还包括两条关键行为:试算不被未结清校验拦截、而落账路径仍被拦截;存量单走"快照零值 + 房价计划现配"的回退路径正确报价。

根因

三层,一层比一层根本:

  1. 校验排序吃掉了试算:只读路径被拒绝类业务校验前置短路。错误码的优先级设计,决定了功能能不能"活着"到达用户——这不是计算 bug,是路径 bug;
  2. 派生关系缺失:只要前端还有一套自己的计价实现,漂移只是时间问题。正确形态不是"多处算、对齐数",而是所有下游展示由一次服务端试算派生——确认按钮、收款金额、提示文案全部如此;
  3. 存量数据的零值双义:升级前入住的存量单,规则快照是合法的零值。"零 = 没有规则"和"零 = 不收超时费"是两个语义,混用会让存量单在新逻辑里永远不出超时费。修复走报价回退:快照缺失时按规则现算,而不是把零值当结论。

约束与取舍

  • 放弃了前端独立计价。写起来自由、改起来灵活,但两套实现必然漂移——这不是纪律能守住的,是结构问题;
  • 房态图悬浮卡降级为粗估。展示层不再追求精确,明文标注"以退房结算试算为准"。放弃悬浮卡上的"精确感",换来全站金额只有一个出处;
  • 每次打开弹窗多一次试算往返。结算弹窗从"静态字段渲染"变成"打开即调用",体验上多一个网络来回;
  • 错误码语义变复杂。试算模式要放宽部分落账校验,哪些校验在试算下跳过、为什么安全,需要专门文档化——这是"读路径先于校验返回"付出的理解成本。

可复用检查清单

  1. 每个对外展示的金额,先回答"它从哪一次计算派生"——答不出单一来源即违规;
  2. 只读试算路径必须排在所有拒绝类业务校验之前,且试算模式跳过的校验要列清单逐条评估副作用;
  3. 升级引入新计费规则时,单独断言"升级前存量数据的默认值语义"——零值是不是合法状态要显式回答;
  4. 前端一切估算必须带"以正式结算为准"的明示;估算精度不足宁可降级,不可伪装精确。

小结

净额问题的解法不是把公式算对,而是把公式只算一次:服务端试算负责算,其余一切负责派生。金额一致性的本质是派生关系的管理,不是数值的巧合一致。