以下情境为教学假设,人物与数字不代表经核实的真实案例。
利润表最容易让人产生「算对了」的错觉。收入减成本减费用等于利润,看起来只有一条公式;但同一笔钱放进收入、变动成本还是运营费用,会得到完全不同的毛利率和 SDE。三表建模的第一步不是把数字算得更细,而是先把口径钉死,让后面两张表都能接得上。
三个案例里,LedgerLite 的利润表最适合做主线:收入来自 260 家客户、每家有月费,成本费用能拆到具体科目,税额和净利润也与现金流、资产负债表对得上。Tidecast 和 Nord Studio 不提供完整的三表,但它们的收入结构足够说明买断与服务在口径上的差异。
收入的三层口径
同一笔钱在利润表里有三个不同的名字:标价收入、净得收入、确认收入。标价收入是价目表上的金额;净得收入是扣掉平台分成、退款、支付费之后真正属于开发者的金额;确认收入是按履约进度可以记进利润表的金额。三者只有在「售出即收款、无平台、无预收」时才恰好相等。
LedgerLite 的确认收入是 240,240 美元。它的来源是 260 家客户乘以每月 77 美元,得到 MRR 20,020 美元,再乘以 12 得到 ARR 240,240 美元。年付客户占 30%,年付折扣 15%,这部分现金在年初收进来,但利润表只能按月确认,差额进入递延收入。利润表上的 240,240 是十二个月确认额的总和,不是当年收到的现金。
Tidecast 的口径更绕。9,600 份乘以标价 19.99 美元,得到标价收入 191,904 美元;退款 4% 扣 7,676 美元;Steam 的 7,680 份再扣 30% 分成 44,215 美元;官网的 1,920 份扣 5% 支付费 1,842 美元;最后净得 138,171 美元。案例事实表把这 138,171 直接作为利润表的收入,所以它的顶行不是 191,904。
Nord Studio 没有平台分成。咨询收入是 110 小时乘以 85 美元再乘以 12,等于 112,200 美元;产品收入是 90 家乘以 39 美元再乘以 12,等于 42,120 美元;合计 154,320 美元。服务按交付确认,产品按月确认,两者都直接进入收入。
| 案例 | 标价或合同收入 | 收入前扣减 | 利润表收入 | 口径说明 |
|---|---|---|---|---|
| LedgerLite | $240,240 | $0 | $240,240 | 订阅按月确认,年付预收进递延收入 |
| Tidecast | $191,904 | 退款 $7,676 + 平台 $44,215 + 支付 $1,842 = $53,733 | $138,171 | 净得口径,平台费用不计入运营费用 |
| Nord Studio | $154,320 | $0 | $154,320 | 服务按交付、产品按月确认 |
这张表最重要的不是数字,而是「扣减项放在哪里」。Tidecast 的 53,733 美元可以在收入前扣除,也可以作为变动成本列在毛利之上,还可以塞进运营费用。三种做法都能得到相同的 SDE 121,171,但毛利率和运营费用率会给出不同的画面:收入前扣除时毛利率 100%;作为变动成本时毛利率 72.0%;塞进运营费用时毛利率仍是 100%,但运营费用从 17,000 变成 70,733(17,000 + 53,733)。三表建模必须先声明采用哪一种,否则后面的比率没有可比性。
收入确认的具体时点、预收与递延的拆分,属于第 4 章的内容;本节只约定利润表顶行用确认收入,详见 4.1 预收、递延与确认时点 。
成本与费用的边界
利润表的第二层口径是成本与费用的分界。变动成本随收入或客户数变化,通常放在毛利之上;运营费用是维持生意运转的固定或可调支出,放在毛利之下。分界不是会计教条,而是为了回答不同的问题:毛利率回答「每多一美元收入,先扣掉多少直接成本」,SDE 回答「扣掉全部运营费用后,所有者还能拿到多少」。
LedgerLite 的 38,000 美元变动成本可以拆成三块:云 18,000、支付 12,000、支持 8,000。云和支付随客户数与收款流水变化,支持随客户数增长但有一定阶梯,属于半变动。72,000 美元运营费用拆成五块:营销 16,800、工具 9,000、外包 20,000、法律保险 6,000、其他 20,200。营销可以按年获客计划调整,工具是固定订阅,外包是产品开发投入,法律保险是合规固定成本。
| 科目 | 金额 | 归类 | 判断理由 | 是否随收入变化 |
|---|---|---|---|---|
| 云 | $18,000 | 变动成本 | 随客户与用量增长 | 是 |
| 支付 | $12,000 | 变动成本 | 随收款流水增长 | 是 |
| 支持 | $8,000 | 变动成本 | 随客户数增长,有阶梯 | 半变动 |
| 营销 | $16,800 | 运营费用 | 获客投入,不随当期收入 | 可调 |
| 工具 | $9,000 | 运营费用 | 固定订阅 | 否 |
| 外包 | $20,000 | 运营费用 | 产品开发投入 | 可调 |
| 法律保险 | $6,000 | 运营费用 | 合规固定支出 | 否 |
| 其他 | $20,200 | 运营费用 | 杂项,混合性质 | 混合 |
创始人薪酬 60,000 美元不在这 72,000 里,而是放在 SDE 之下。SDE 的定义是「扣除运营费用、但尚未扣除所有者薪酬」的收益;如果把 60,000 放进运营费用,算出来的 70,240 就不是 SDE,而是税前利润。这个顺序不是格式问题,而是估值口径问题:SDE 130,240 和税前利润 70,240 会被套上不同的倍数。
Nord Studio 提供了一个更明显的分类例子。案例把两位合伙人的 96,000 美元工资放在毛利之下,所以毛利是 130,320,毛利率 84.4%,SDE 是 34,320。如果把合伙人工资视为交付服务的直接人力成本,毛利会降到 34,320,毛利率变成 22.2%,运营费用变成 0,SDE 仍然是 34,320。分类改变了毛利率,没有改变 SDE;但如果拿毛利率去和纯产品公司比较,22.2% 和 84.4% 会给出完全不同的印象。
Tidecast 的 100% 毛利率也是分类的产物。它的平台分成、退款和支付费已经在收入前扣掉,利润表上只剩 138,171 的净得,变动成本为 0,所以毛利率是 100%。如果改用总额口径,收入 191,904,扣减项 53,733 作为变动成本,毛利率是 72.0%。这个 72.0% 与净得率 138,171 ÷ 191,904 是同一个比值,只是在利润表里叫毛利率,在渠道分析里叫净得率;净得口径下的 100% 才是不同的分母。
| 口径 | 收入 | 毛利 | 毛利率 | SDE | 说明 |
|---|---|---|---|---|---|
| LedgerLite 案例口径 | $240,240 | $202,240 | 84.2% | $130,240 | 变动成本 $38,000,运营费用 $72,000 |
| Tidecast 净得口径 | $138,171 | $138,171 | 100.0% | $121,171 | 平台费与退款已在收入前扣除 |
| Tidecast 总额口径 | $191,904 | $138,171 | 72.0% | $121,171 | 扣减项 $53,733 作为变动成本 |
| Nord 案例口径 | $154,320 | $130,320 | 84.4% | $34,320 | 合伙人工资在毛利之下 |
| Nord 工资进成本 | $154,320 | $34,320 | 22.2% | $34,320 | 运营费用变为 $0,SDE 不变 |
本节采用案例事实表的分类:LedgerLite 的创始人薪酬在 SDE 之下,Tidecast 用净得口径,Nord 的合伙人工资在毛利之下。简化假设是:没有折旧、没有利息、没有汇兑损益;税只用单一税率,不处理亏损结转和税收抵免。这些假设会在第 3.2 节和第 3.3 节继续沿用,直到三张表能闭合。
LedgerLite 利润表逐项复算
把 LedgerLite 从客户数一路算到净利润,一共八步。每一步只用一个加减乘除,派生值都写明公式。
| 步骤 | 公式 | 结果 |
|---|---|---|
| MRR | 260 家 × $77 | $20,020 |
| ARR | $20,020 × 12 | $240,240 |
| 毛利 | $240,240 − $38,000 | $202,240 |
| 毛利率 | $202,240 ÷ $240,240 | 84.2% |
| SDE | $202,240 − $72,000 | $130,240 |
| 税前利润 | $130,240 − $60,000 | $70,240 |
| 所得税 | 17% × $70,240 | $11,940.80,案例表取整为 $11,941 |
| 净利润 | $70,240 − $11,941 | $58,299 |
这里有一个容易被忽略的舍入差。案例事实表按整数美元展示,所以税额写 11,941,净利润写 58,299;如果按美分精确计算,17% 乘以 70,240 是 11,940.80,净利润是 58,299.20。两者相差 0.20 美元,不影响任何结论,但代码和正文必须使用同一套舍入口径。本节的表格用案例表的整数美元,代码用精确到分的美分。
把利润表的各项换成占收入的比率,可以看出费用结构:
| 指标 | 金额 | 占收入 |
|---|---|---|
| 收入 | $240,240 | 100.0% |
| 变动成本 | $38,000 | 15.8% |
| 毛利 | $202,240 | 84.2% |
| 运营费用 | $72,000 | 30.0% |
| SDE | $130,240 | 54.2% |
| 创始人薪酬 | $60,000 | 25.0% |
| 税前利润 | $70,240 | 29.2% |
| 所得税 | $11,941 | 5.0% |
| 净利润 | $58,299 | 24.3% |
毛利率 84.2% 减运营费用率 30.0% 等于 SDE 率 54.2%;再减创始人薪酬率 25.0% 得到 29.2% 的税前利润率;再减 5.0% 的税,得到 24.3% 的净利率。按精确金额计算,这条链能闭合;表中四舍五入到一位小数,逐项相加可能有 0.1 个百分点的舍入差。分类没有重复也没有遗漏。
但 130,240 的 SDE 不是现金。它还没有扣营运资金变化、资本开支和债务偿还;净利润 58,299 也不是现金,因为收入里有一部分是应收,成本里有一部分是预收。利润表只回答「这一年赚没赚」,不回答「现金有没有增加」。下一节的现金流量表会从净利润出发,把这两件事拆开。
买断与服务的对照
把三个案例的利润表放在一起,差异很快显现。
| 指标 | LedgerLite | Tidecast | Nord Studio |
|---|---|---|---|
| 收入 | $240,240 | $138,171 | $154,320 |
| 收入性质 | 经常性订阅 | 一次性买断 | 72.7% 服务 + 27.3% 产品 |
| 变动成本 | $38,000 | $0(净得口径) | $24,000 |
| 毛利 | $202,240 | $138,171 | $130,320 |
| 毛利率 | 84.2% | 100.0% | 84.4% |
| 运营费用 | $72,000 | $17,000 | $96,000 |
| SDE | $130,240 | $121,171 | $34,320 |
| SDE 率 | 54.2% | 87.7% | 22.2% |
| 所得税 | $11,941 | $29,081 | $8,580 |
| 净利润 | $58,299 | $92,090 | $25,740 |
| 净利率 | 24.3% | 66.6% | 16.7% |
表中 SDE 率 = SDE ÷ 收入,净利率 = 净利润 ÷ 收入。Tidecast 的净利润是 121,171 − 29,081 = 92,090,Nord 的净利润是 34,320 − 8,580 = 25,740;LedgerLite 的净利润 58,299 已在上一节复算。
Tidecast 的 SDE 率 87.7%、净利率 66.6%,看起来是三个案例里最赚钱的。这组数字没有错,但它只描述「已经卖出去的 9,600 份」。买断收入是一次性的,明年不保证还有 9,600 份;12 月卖 2,400 份、1 月卖 480 份,Q4 占全年销量 38%,现金高度集中在假日季。利润表上的高净利率,是用一次性收入换来的。
Nord Studio 的 SDE 率只有 22.2%,因为 96,000 美元工资是刚性的。服务收入占 72.7%,咨询续约率 70%,意味着每年有 30% 的服务收入需要重新赢得;产品收入占 27.3%,月流失 3.0%。毛利率 84.4% 看起来健康,但毛利要先养活两个人,剩下的才是 SDE。这也是为什么它的标准估值锚 4.5 倍 SDE 只有 154,440 美元,而且因为服务收入占比高、依赖合伙人,买方报价还会下调到 3.0 倍 SDE = 102,960 美元。
LedgerLite 处在中间:SDE 率 54.2%,净利率 24.3%,收入是经常性的,月流失 1.5%、NRR 102%、GRR 83%。它的运营费用 72,000 不是浪费,而是维持下一年的收入基础;营销 16,800 对应年新签 120 家,CAC 140 美元,回本周期 2.2 个月。利润表上的费用,要和单位经济一起看才有意义;单位经济的四个数见 2.1 单位经济的四个数:ARPA、毛利、CAC、回本 。
三种生意的利润表差异,最终落在三个位置:
- 订阅的差异在留存和获客成本:毛利率高,运营费用也高,但收入可以续。
- 买断的差异在销量和平台分成:单笔净得高,收入不经常,季节性强。
- 服务的差异在人的成本:毛利率看起来健康,SDE 被工资吃掉,收入依赖合伙人。
利润表能解释「为什么 SDE 不同」,但不能解释「为什么现金不同」。Tidecast 的 92,090 净利润和 LedgerLite 的 58,299 净利润,哪一个更能变成手里的现金,要看应收、递延收入和资本开支。这正是下一张表要回答的问题。
把利润表写成代码
下面的 income-statement.ts 把收入拆成经常性和一次性两桶,再复用第 1 章的 pnl.ts 计算毛利、SDE、税前利润、税和净利润。金额统一用整数美分,比率统一用基点,非法输入返回 Result 错误,而不是静默算出一个看起来合理的数。
// file: src/fin/income-statement.ts
// tier: L1
// verify: node --test test/fin/income-statement.test.ts
import { type Cents, sum } from "../core/money.ts";
import { type Bps, divide } from "../core/rate.ts";
import { type Result, fail, ok } from "../core/result.ts";
import { type Pnl, buildPnl } from "./pnl.ts";
export type RevenueKind = "recurring" | "one-time";
export type RevenueLine = {
name: string;
kind: RevenueKind;
amountCents: Cents;
};
export type StatementLine = RevenueLine & {
shareBps: Bps;
};
export type RevenueSplit = {
recurringCents: Cents;
oneTimeCents: Cents;
totalCents: Cents;
recurringShareBps: Bps;
};
export type IncomeStatement = {
lines: StatementLine[];
split: RevenueSplit;
grossProfit: Cents;
grossMarginBps: Bps;
sde: Cents;
pretax: Cents;
tax: Cents;
net: Cents;
};
function validate(lines: RevenueLine[]): Result<Cents> {
if (lines.length === 0) {
return fail("at least one revenue line is required");
}
for (const line of lines) {
if (line.name.trim() === "") {
return fail("revenue line name must not be empty");
}
if (line.kind !== "recurring" && line.kind !== "one-time") {
return fail(`unknown revenue kind for ${line.name}`);
}
if (!Number.isInteger(line.amountCents) || line.amountCents < 0) {
return fail(`invalid amount for ${line.name}`);
}
}
const total = sum(lines.map((line) => line.amountCents));
if (total <= 0) {
return fail("total revenue must be positive");
}
return ok(total);
}
/**
* Splits revenue into recurring and one-time buckets. The classification is
* supplied by the caller: the same contract can be recurring or one-time
* depending on the question being asked.
*/
export function splitRevenue(lines: RevenueLine[]): Result<RevenueSplit> {
const checked = validate(lines);
if (!checked.ok) {
return checked;
}
const recurringCents = sum(
lines
.filter((line) => line.kind === "recurring")
.map((line) => line.amountCents),
);
return ok({
recurringCents,
oneTimeCents: checked.value - recurringCents,
totalCents: checked.value,
recurringShareBps: divide(recurringCents, checked.value),
});
}
/**
* Builds an income statement from revenue lines and the same four cost and
* tax inputs used by pnl.ts. Line shares are reported in basis points.
*/
export function buildIncomeStatement(input: {
lines: RevenueLine[];
variableCosts: Cents;
operatingExpenses: Cents;
founderSalary: Cents;
taxRate: Bps;
}): Result<IncomeStatement> {
const split = splitRevenue(input.lines);
if (!split.ok) {
return split;
}
const pnl: Result<Pnl> = buildPnl({
revenue: split.value.totalCents,
variableCosts: input.variableCosts,
operatingExpenses: input.operatingExpenses,
founderSalary: input.founderSalary,
taxRate: input.taxRate,
});
if (!pnl.ok) {
return pnl;
}
return ok({
lines: input.lines.map((line) => ({
...line,
shareBps: divide(line.amountCents, split.value.totalCents),
})),
split: split.value,
grossProfit: pnl.value.grossProfit,
grossMarginBps: pnl.value.grossMargin,
sde: pnl.value.sde,
pretax: pnl.value.pretax,
tax: pnl.value.tax,
net: pnl.value.net,
});
}
代码只做三件事:校验收入行、拆分经常性与一次性、把总额交给 buildPnl。RevenueLine.kind 由调用方决定:LedgerLite 的订阅是 recurring,Tidecast 的 Steam 和官网都是 one-time,Nord 的咨询在本节按 one-time、产品按 recurring,因为案例事实表把产品收入单独列为 27.3%。这个分类不是永久标签,而是服务于当前问题的视角。
LedgerLite 的输入可以直接从事实表抄进夹具:收入行 24,024,000 美分、变动成本 3,800,000、运营费用 7,200,000、创始人薪酬 6,000,000、税率 1,700 bps。输出是毛利 20,224,000、毛利率 8,418 bps、SDE 13,024,000、税前 7,024,000、税 1,194,080、净利润 5,829,920。Tidecast 和 Nord 的输入也能得到对应结果,测试文件里各有断言。
这套代码不处理折旧、摊销、利息、汇兑和一次性项目,也不处理多币种换算。它只覆盖本章的简化口径;真实账目里的科目映射、月结和审计调整,属于记账与月结流程,见 记账与月结 。
小结
- 利润表的第一口径是收入:标价、净得、确认收入三者不能混用。Tidecast 从 191,904 到 138,171 是净得口径,净得率 72.0%。
- 第二口径是成本与费用:LedgerLite 变动成本 38,000、运营费用 72,000;创始人薪酬 60,000 在 SDE 之下,不在运营费用里。
- 第三口径是分类:Nord 的 96,000 工资放在毛利下还是成本里,毛利率从 84.4% 变成 22.2%,SDE 仍然是 34,320。
- LedgerLite 从 MRR 20,020 到净利润 58,299,八步全部可复算;SDE 130,240 不是现金,净利润 58,299 也不是现金。
- 三案例 SDE 分别为 130,240、121,171、34,320,差异来自收入性质与固定成本,不是会计技巧。
- 利润表只回答「赚没赚」;下一张现金流量表回答「现金有没有增加」,再下一张资产负债表回答「期末还剩什么」。
阅读导航:上一节:2.3 单位经济失效的五个信号 · 下一节:3.2 现金流量表:经营、投资、筹资 。
常见错误与边界
- 把标价收入当利润表收入。 Tidecast 的标价收入是 191,904,利润表收入是净得 138,171;用 191,904 算毛利率会得到 72.0%,但分母已经不是利润表的顶行。
- 把平台分成放进运营费用。 净得口径下,平台分成和退款已经在收入前扣除;再放进运营费用会重复扣减,SDE 会被低估。
- 把创始人薪酬放进运营费用。 LedgerLite 的 60,000 如果放进运营费用,SDE 会从 130,240 变成 70,240;这个数字是税前利润,不是 SDE,套估值倍数会错位。
- 用毛利率比较不同收入口径。 Tidecast 的 100% 和 72.0% 是同一笔生意的两种展示;不声明口径就横向比较,结论没有意义。
- 把 SDE 当净利润或现金。 SDE 还没扣创始人薪酬、所得税、营运资金变化和资本开支;LedgerLite 的 SDE 130,240 与净利润 58,299 相差 71,941(130,240 − 58,299)。
- 忽略税额舍入。 案例表写 11,941,精确值是 11,940.80;正文与代码要用同一套舍入口径,否则三表会出现几分钱的差。
- 把一次性买断收入按订阅倍数估值。 Tidecast 的估值锚是 1.8 倍收入,LedgerLite 是 3.2 倍 ARR;倍数差异来自收入性质,不是谈判技巧。
- 认为科目分类只有一种正确答案。 Nord 的工资进成本后毛利率变成 22.2%,SDE 不变;分类服务于问题,但必须在表头声明。
- 忘记递延收入。 LedgerLite 的年付预收不是当期收入,递延收入增加 8,000 会在现金流量表里加回;只看利润表会低估订阅业务的现金。
- 把利润表当成现金表。 利润表按权责发生制确认,现金按收付实现制流动;两者之间的桥,就是下一节的现金流量表。
本节的边界是「利润表内部的口径」:收入怎么定义、成本费用怎么分、SDE 和净利润怎么算。它不处理收入确认的具体时点、不处理记账软件操作、不处理税务申报流程;这些分别属于第 4 章、记账与月结流程和税务章节。税务申报流程不在本节展开,税率只作为利润表参数;所得税与常设机构的口径见 9.2 所得税与常设机构 。把这三层口径固定下来,3.2 节才能从净利润出发,逐项解释现金为什么增加或减少。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。