以下情境为教学假设,人物与数字不代表经核实的真实案例。
3.2 节的账龄表告诉你应收有多老,但没有告诉你这些钱什么时候能回来。一笔 31–60 天的应收,可能在第 45 天到账,也可能拖到第 120 天;两者在账龄表上属于同一档,在现金流上差了两个多月。要回答“什么时候到账”,需要把开票、对账、提醒和升级拆成一条时间线,再给每个节点一个概率。
这一节的核心不是催收话术,而是把催收节奏折算成期望到账时间。LedgerLite 的账期是 Net 15,逾期 30 天以上占 6%;Nord Studio 是 Net 30,逾期占 8%;Tidecast 面对平台月结 30 天,应收为 0。三个案例的催收对象不同,但计算方法是同一个:先把回款写成分布,再把每个节点的动作折算成分布的变化。
到期日只是一个点,到账是一条分布
一张发票的时间线至少有四个日期:开票日、到期日、实际付款日、现金可用日。合同只约定到期日,现金却由实际付款日决定。把实际付款日画成分布,通常会看到大多数客户在到期日附近付款,少数客户拖到 +7、+30、+60,极少数进入 90 天以上。
用概率描述这条分布,期望到账天数可以写成:
期望到账天数 = 账期 + Σ(节点偏移天数 × 该节点概率)
LedgerLite 的 Net 15 用一组教学曲线表示:83% 在到期日付款,11% 晚 7 天,4% 晚 30 天,2% 晚 60 天。把偏移天数加权:
| 节点 | 到账日 | 概率 | 累计已付 | 仍未付 | 对期望天数的贡献 |
|---|---|---|---|---|---|
| 到期日 | 第 15 天 | 83% | 83% | 17% | 12.45 天 |
| +7 天 | 第 22 天 | 11% | 94% | 6% | 2.42 天 |
| +30 天 | 第 45 天 | 4% | 98% | 2% | 1.80 天 |
| +60 天 | 第 75 天 | 2% | 100% | 0% | 1.50 天 |
| 合计 | — | 100% | — | — | 18.17 天 |
期望到账天数是 18.17 天,比合同账期多 3.17 天。这个结果与 LedgerLite 的实际应收周转天数 18.2 天几乎一致,说明这组概率不是随手编的,而是按案例的实际账龄反推出来的教学曲线。注意“逾期 30 天以上占 6%”对应的是 +30 的 4% 加 +60 的 2%,不是 17% 的未付比例;未付不等于逾期,到期日当天还有 17% 没付,但其中大部分会在 7 天内到账。
Nord Studio 的 Net 30 用另一组曲线:78% 在到期日付款,14% 晚 7 天,5% 晚 30 天,3% 晚 60 天。期望到账天数是 34.28 天,比合同多 4.28 天。但 Nord 的实际混合账龄是 26.6 天,低于 30 天,原因是 27.3% 的产品订阅按月预付;如果只看咨询业务,34.28 天才是更接近现实的数字。混合账龄把预付款的零账期和咨询的 Net 30 加权在一起,不能直接拿来判断催收效率。
Tidecast 是另一种结构。它没有客户应收,只有平台月结:Steam 销售在月末结算,30 天后到账,应收记为 0。期望到账天数就是 30 天,分布几乎是一个点,因为平台不会“忘记付款”,也不会因为对账争议拖到 90 天。平台结算的风险不是逾期,而是平台封禁、扣款或分成调整,那属于渠道依赖,不是应收管理。Tidecast 的 13 周现金表已经把 30 天延迟画成脉冲后的长尾,见 8.1 13 周现金流表 。
开票触发条件:先让债权成立
开票不是把金额发出去,而是把“我为你做了事”变成“你欠我一笔可执行的款”。如果开票时缺少关键字段,发票会在客户内部被搁置,到期日从“合同写的第 15 天”变成“客户内部流程走完的那一天”。这段延迟不在合同里,却在现金表里。
开票前至少要确认五件事:交付或验收已经发生、合同或采购订单编号可引用、开票主体与税号正确、金额与对账单一致、收件人是能处理发票的人。缺任何一项,发票都可能进入争议状态;争议期间客户不付款,也不一定告诉你缺什么。
把这段延迟换算成钱:一张 10,000 美元的发票,如果因为信息缺失晚开 30 天,按 8% 资金成本,成本是 65.75 美元,等于把 Net 30 又延长了一个月。LedgerLite 的月收入是 20,020 美元,晚开一个月等于多占用 20,020 美元 30 天,资金成本约 131.64 美元;Nord 的月收入是 12,860 美元,对应约 84.56 美元。这些数字不是罚款,而是现金被无谓地留在客户手里的价格。
| 场景 | 延迟天数 | 占用金额 | 8% 年成本 |
|---|---|---|---|
| 单张 $10,000 发票 | 30 | $10,000 | $65.75 |
| LedgerLite 一个月收入 | 30 | $20,020 | $131.64 |
| Nord Studio 一个月收入 | 30 | $12,860 | $84.56 |
开票条件的严谨程度,直接决定后面催收的起点。一张字段齐全、金额可对、收件人明确的发票,到期日就是真正的到期日;一张缺 PO、缺验收、金额有争议的发票,到期日只是一个写在纸上的日期。付款条款本身的设计,见 1.3 价格、调价与付款条款:金额、时点、前提与违约责任 ;本节只处理条款生效之后的执行。
对账单:把争议在到期前清零
对账单不是催款单,它是一张双方都能核对的余额表:期初余额、本期开票、本期收款、贷项通知单、期末余额。它的作用是在到期日之前把“哪张发票、多少钱、付了没有”对齐,而不是等到逾期后再解释。
如果对账只在逾期后做,争议会直接变成账龄。假设 10% 的发票存在争议,每个争议平均多花 21 天解决,那么期望到账天数会增加 0.1 × 21 = 2.1 天。这个比例是教学假设,不是案例事实;它的用途是展示对账节奏的现金价值。
| 案例 | 年收入 | 期望天数增加 | 多占用现金 | 8% 年成本 |
|---|---|---|---|---|
| LedgerLite | $240,240 | 2.1 | $1,382 | $111 |
| Nord Studio | $154,320 | 2.1 | $888 | $71 |
2.1 天看起来很小,但它会把账龄表里的当前档推向 31–60 档。一笔发票在争议中停留 21 天,可能从 0–30 档跳到 31–60 档,准备率从 1% 变成 5%;如果争议拖到 61 天,准备率变成 20%。对账的价值不只是提前收款,还包括阻止发票在账龄表里“变老”。
对账频率要与账期匹配。Net 15 的客户,月度对账太慢,至少要在月中和月末各对一次;Net 30 的客户,月度对账可以接受,但要在到期前完成,而不是到期后补。对账的对象不是所有客户,而是有多个项目、多个 PO、多个付款批次的客户;单一订阅、自动扣款的客户,对账成本高于收益。
催收节奏:每个节点对应一段概率
催收不是“到期后开始催”,而是一条从到期前到 90 天以上的阶梯。每个节点做一件事,把对应的一段概率往前推。下面以 LedgerLite 的 Net 15 曲线为例。
| 节点 | 目的 | 累计已付 | 仍未付 | 对期望天数的影响 |
|---|---|---|---|---|
| 到期前 3 天 | 确认发票已收到、金额无争议 | 0% | 100% | 把“没收到发票”的延迟移出分布 |
| 到期日 | 确认付款已发起 | 83% | 17% | 12.45 天 |
| +7 天 | 找到审批或付款流程的卡点 | 94% | 6% | 2.42 天 |
| +30 天 | 升级到财务负责人或客户老板 | 98% | 2% | 1.80 天 |
| +60 天 | 书面通知、暂停新工作或转正式催收 | 100% | 0% | 1.50 天 |
| +90 天 | 进入法律或第三方催收评估 | — | — | 不再按普通应收管理 |
这张表的关键不是“每个节点发什么”,而是每个节点改变哪一段概率。到期前 3 天的提醒,不直接产生现金,但它把一部分“发票没收到”“金额对不上”的延迟从分布里拿掉;+7 天的跟进,把付款流程卡住的 11% 拉回来;+30 天和 +60 天的升级,处理的是剩下的 4% 和 2%。如果跳过前两个节点,直接在第 30 天催,前面本可以早收的钱已经变成了账龄。
把“有节奏”和“无节奏”放在一起比较,差距可以量化。无节奏的 LedgerLite 曲线是 65% 到期付、15% +7、12% +30、8% +60,期望到账 24.45 天;有节奏的曲线是 18.17 天,相差 6.28 天。按年收入 240,240 美元计算,6.28 天对应的平均现金占用是 4,133 美元,按 8% 计算的年成本是 331 美元。
| 案例 | 有节奏 | 无节奏 | 节省天数 | 释放现金 | 8% 年成本节省 |
|---|---|---|---|---|---|
| LedgerLite | 18.17 天 | 24.45 天 | 6.28 | $4,133 | $331 |
| Nord 咨询 | 34.28 天 | 40.86 天 | 6.58 | $2,023 | $162 |
Nord 的咨询收入是 112,200 美元,6.58 天对应 2,023 美元的现金释放,年成本节省 162 美元。数字不大,但这是每年重复的余额效应,不是一次性收益。更重要的是尾部:LedgerLite 的无节奏曲线有 20% 的发票超过 30 天,有节奏曲线降到 6%;Nord 从 22% 降到 8%;90+ 的金额减少,坏账准备和催收成本也跟着下降。4.1 节会把这些尾部损失折算成准备,4.2 节会处理催收函、律师函和仲裁的成本。
催收节奏还有一条边界:不是越早越好,也不是越硬越好。到期前 3 天提醒是确认,不是施压;+7 天跟进是解决问题,不是指责;+30 天升级是找对人,不是威胁;+60 天书面通知是保留权利,不是撕破脸。每一步的力度要和合同条款匹配;合同没有约定暂停服务或加速到期的权利,催收动作就只能停留在沟通层面。条款层面的救济,见 4.2 催收升级与法律途径的成本收益 。
把开票与催收写成代码
下面的 invoice.ts 把回款分布和催收节奏固化成两个函数:measureCurve 输入账期和每个节点的概率,输出期望到账天数、按时比例、逾期比例和 90+ 比例;cadenceGain 比较两条曲线,输出节省天数、释放现金和年资金成本节省。金额用整数美分,概率用基点,概率之和必须是 10000。
// file: src/contract/invoice.ts
// tier: L1
// verify: node --test test/contract/invoice.test.ts
import { type Cents } from "../core/money.ts";
import { type Bps, applyRate } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
export const DAYS_PER_YEAR = 365;
export type CollectionStage = {
/** Days after the due date. Zero is paid on the due date. */
dayOffset: number;
probabilityBps: Bps;
};
export type CurveMetrics = {
expectedLateDays: number;
expectedDaysToCash: number;
onTimeBps: Bps;
overdueBps: Bps;
ninetyPlusBps: Bps;
};
export type CadenceGain = {
daysSaved: number;
cashReleased: Cents;
annualCostSaved: Cents;
};
/**
* Cash arrival is a distribution. Each stage carries a probability, and the
* expected day is the probability-weighted average of invoice date plus terms
* plus the stage offset.
*/
export function measureCurve(
termsDays: number,
stages: CollectionStage[],
): Result<CurveMetrics> {
if (!Number.isInteger(termsDays) || termsDays < 0 || termsDays > 365) {
return fail("terms must be an integer between 0 and 365 days");
}
if (stages.length === 0) {
return fail("at least one collection stage is required");
}
let totalBps = 0;
let weightedOffset = 0;
let onTimeBps = 0;
let overdueBps = 0;
let ninetyPlusBps = 0;
for (const stage of stages) {
if (
!Number.isInteger(stage.dayOffset) ||
stage.dayOffset < -365 ||
stage.dayOffset > 365
) {
return fail("day offset must be an integer between -365 and 365");
}
if (
!Number.isInteger(stage.probabilityBps) ||
stage.probabilityBps < 0 ||
stage.probabilityBps > 10000
) {
return fail("probability must be an integer between zero and 10000 bps");
}
totalBps += stage.probabilityBps;
weightedOffset += stage.dayOffset * stage.probabilityBps;
if (stage.dayOffset <= 0) {
onTimeBps += stage.probabilityBps;
}
if (stage.dayOffset >= 30) {
overdueBps += stage.probabilityBps;
}
if (stage.dayOffset > 90) {
ninetyPlusBps += stage.probabilityBps;
}
}
if (totalBps !== 10000) {
return fail("collection probabilities must sum to 10000 bps");
}
const expectedLateDays = weightedOffset / 10000;
const expectedDaysToCash = termsDays + expectedLateDays;
return ok({
expectedLateDays: Math.round(expectedLateDays * 100) / 100,
expectedDaysToCash: Math.round(expectedDaysToCash * 100) / 100,
onTimeBps,
overdueBps,
ninetyPlusBps,
});
}
/**
* Compares two curves for the same terms. The cash released is the annual
* revenue multiplied by the days saved and divided by 365; it is an average
* balance, not a one-off cash receipt.
*/
export function cadenceGain(
annualRevenue: Cents,
baseline: CurveMetrics,
cadence: CurveMetrics,
costOfCapitalBps: Bps,
): Result<CadenceGain> {
if (!Number.isInteger(annualRevenue) || annualRevenue < 0) {
return fail("annual revenue must be a non-negative integer");
}
if (
!Number.isInteger(costOfCapitalBps) ||
costOfCapitalBps < 0 ||
costOfCapitalBps > 10000
) {
return fail("cost of capital must be an integer between zero and 10000 bps");
}
const daysSaved =
Math.round((baseline.expectedDaysToCash - cadence.expectedDaysToCash) * 100) /
100;
const cashReleased = Math.round((annualRevenue * daysSaved) / DAYS_PER_YEAR);
return ok({
daysSaved,
cashReleased,
annualCostSaved: applyRate(cashReleased, costOfCapitalBps),
});
}
把 LedgerLite 的有节奏曲线代入 measureCurve(15, ...),得到期望迟到 3.17 天、期望到账 18.17 天、按时 8,300 基点、逾期 600 基点;无节奏曲线得到 24.45 天和 2,000 基点逾期。cadenceGain 比较两者,得到节省 6.28 天、释放 413,344 美分、年成本节省 33,068 美分。Nord 的咨询曲线得到 34.28 天和 800 基点逾期;无节奏曲线 40.86 天;节省 6.58 天、释放 202,267 美分、年成本节省 16,181 美分。这些数字与上表逐行对应。
代码里有三个刻意的设计。第一,节点偏移可以是负数,用来表示提前付款;这让“早付折扣”和“到期付款”能用同一个模型比较。第二,逾期比例按“到期后 30 天及以上”统计,与账龄表的 31–60 档起点对齐;如果按“大于 30 天”统计,+30 的 4% 会被漏掉。第三,cadenceGain 的现金释放是平均余额,不是一次性收入;把它当成利润会高估催收的价值。
小结
- 现金到账是一条分布:期望到账天数 = 账期 + Σ(节点偏移 × 概率),不是合同上的到期日。
- LedgerLite 的 Net 15 教学曲线得到 18.17 天,与案例实际 18.2 天吻合;Nord 的咨询曲线得到 34.28 天,混合账龄 26.6 天被产品预付款拉低。
- 开票前缺少验收、PO、税号、金额或收件人,会把到期日推迟;一张 $10,000 发票晚开 30 天,资金成本是 $65.75。
- 对账把争议在到期前清零;10% 发票争议、每个多 21 天,会把期望天数拉长 2.1 天,并推动账龄升级。
- 催收阶梯为到期前 3 天、到期日、+7、+30、+60、+90;每个节点对应一段概率,不是一套话术。
- LedgerLite 有节奏比无节奏少 6.28 天,释放 $4,133 现金,年资金成本节省 $331;Nord 咨询少 6.58 天,释放 $2,023,节省 $162。
- 催收的主要收益不在 8% 的资金成本,而在减少 90+ 尾部和坏账准备;4.1 与 4.2 会继续算这部分。
阅读导航:上一节:3.2 应收账龄表:从余额到风险分档 · 下一节:4.1 坏账准备:计提、核销与转回 。
常见错误与边界
- 把到期日当到账日。 LedgerLite 的合同是 Net 15,期望到账是 18.17 天;按 15 天排现金会低估占用。
- 用发票日期算账龄。 账龄和催收都按到期日计算;按开票日会把 60 天账期的发票误判为逾期。
- 开票前不确认验收和 PO。 缺少字段的发票会进入争议,30 天的延迟等于把账期延长一个月,单张 $10,000 发票多花 $65.75。
- 把对账留到逾期后。 对账的目标是到期前清零争议;拖到逾期后,发票已经进入 31–60 档,准备率从 1% 变成 5%。
- 跳过到期前提醒。 到期前 3 天的确认不产生现金,但它把“没收到发票”“金额不对”的延迟移出分布;跳过它,后面所有节点都要往后推。
- 催收只靠邮件模板。 模板不改变概率;改变概率的是找对人、确认卡点、给出明确的下一步和期限。
- 过早升级或过晚升级。 +7 天就发律师函会破坏关系;+60 天才第一次跟进,90+ 的尾部已经形成。
- 把现金释放当年利润。 6.28 天释放的 $4,133 是平均余额,不是一次性收入;它降低资金占用,不增加收入。
- 忽略平台结算与客户应收的区别。 Tidecast 的 30 天是平台结算,不是客户逾期;平台不会“忘记付款”,但可能封禁或调整分成。
- 催收动作超出合同权利。 合同没有约定暂停服务、加速到期或收取迟延利息,催收就只能停留在沟通层面;救济条款见 4.2 节。
本节的边界是“单张发票、给定概率曲线”的期望模型。它假设每个节点的概率稳定,不含客户信用差异、争议金额大小和催收成本。它足够回答“这个节奏值多少天、多少钱”,但不足以回答“这笔 90+ 的应收还值不值得追”。后者要把回收概率、催收成本和净回收额放在一起比较,是 4.1 和 4.2 节的内容。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。