复式记账法
Martin Blais,2016 年 12 月
http://furius.ca/beancount/doc/double-entry
引言
本文以计算机科学家的视角,对复式记账法进行通俗介绍。本文试图用尽可能简单的方式解释基础会计原理,摒弃了传统会计中一些繁琐的惯例。同时,本文也反映了 Beancount 的工作方式,对所有使用 纯文本记账 的用户都具有参考价值。
请注意,我并非会计师,在撰写本文过程中,我可能使用了与传统会计培训中所教授的术语略有不同或不常见的表达。为了向不熟悉这些概念的人清晰、简洁地解释这些思想,我允许自己创造一些新颖甚至独特的表述方式。
我相信,复式记账法应当在全球的高中教育中普及,因为它是一种极为有用的组织技能。我希望本文能帮助将这一知识传播到专业圈子之外。
复式记账基础
复式记账法只是一种简单的计数方法,并遵循一些简单的规则。
让我们先定义账户的概念。账户是一种可以容纳事物的容器,就像一个袋子,用于计数和累积事物。我们用一条水平箭头来直观表示账户内容随时间的变化:

左侧代表过去,右侧代表时间的推移:现在、未来等。
目前,我们假设账户只能包含一种事物,例如美元。所有账户的初始内容均为零美元。我们将账户中的单位数量称为账户的余额。请注意,它表示账户在某一特定时间点的内容。我将用账户时间线上方的数字来表示余额:

账户的内容会随时间变化。要改变账户的内容,我们必须向其中添加东西。我们将这种添加称为对账户的记账,并用账户时间线上带圆圈的数字来表示这种变化,例如向账户中添加 100 美元:

现在,我们可以在记账后紧接其后用另一个小数字表示账户更新后的余额:

在添加 100 美元后,该账户的余额现在为 100 美元。
我们也可以从账户中扣除内容。例如,我们可以扣除 25 美元,此时账户余额变为 75 美元:

如果从账户中扣除的金额超过其现有余额,账户余额也可能变为负数。例如,如果我们从该账户中扣除 200 美元,余额现在变为-125 美元:

账户拥有负余额是完全正常的。请记住,我们所做的只是计数。正如我们稍后将看到的,某些账户在其大部分时间线中都会保持负余额。
报表
值得注意的是,我在上一节中使用的时序记法,与金融机构为每位客户维护的纸质账户对账单非常相似,这些对账单通常通过邮件寄送给你:
| 日期 | 描述 | 金额 | 余额 |
|---|---|---|---|
| 2016-10-02 | ... | 100.00 | 1100.00 |
| 2016-10-05 | ... | -25.00 | 1075.00 |
| 2016-10-06 | ... | -200.00 | 875.00 |
| 最终余额 | 875.00 |
有时金额列会被分为两列,一列显示正金额,另一列显示负金额:
| 日期 | 描述 | 借方 | 贷方 | 余额 |
|---|---|---|---|---|
| 2016-10-02 | ... | 100.00 | 1100.00 | |
| 2016-10-05 | ... | 25.00 | 1075.00 | |
| 2016-10-06 | ... | 200.00 | 875.00 | |
| 最终余额 | 875.00 |
在这里,‘借方’表示‘从您的账户中扣除’,‘贷方’表示‘存入您的账户’。有时也会使用‘取款’和‘存款’这两个词。具体用词取决于上下文:对于支票账户和储蓄账户,通常会同时出现这两种记录;但对于信用卡账户,通常只显示正数金额以及偶尔的月度还款,因此采用单列格式。
无论如何,‘余额’列始终显示金额记入账户后的结果余额。此外,有时对账单会按时间递减的顺序排列。
单式记账法
在这个例子中,该账户属于某个人。我们将此人称为账户的所有者。该账户可用于代表现实世界中的账户,例如,我们可以用它来代表所有者在银行的支票账户余额。因此,我们将通过命名来标识该账户,此处命名为“支票”:

假设在某个时间点,该账户余额为 1000 美元,如图所示。现在,如果所有者从该账户支出 79 美元,我们可以这样表示:

此外,如果这笔支出是用于餐厅用餐,我们可以通过添加一个类别来标记这笔记录,以说明资金的用途。例如,标记为“餐厅”,如下所示:

如果我们有大量此类记录,就可以编写一个计算机程序,汇总每个类别的所有变动并计算各自的总额。这样我们就能知道总共在餐厅花了多少钱。这种方法被称为单式记账法。
但我们不会采用这种方式;我们有更好的方法。请继续阅读接下来的几个部分。
复式记账法
所有者可能拥有多个账户。我将通过在同一张图上绘制多个类似的账户时间线来表示这一点。与之前一样,这些账户都用唯一名称标记。假设所有者仍拥有之前的“支票”账户,但现在还新增了一个“餐厅”账户,用于累计所有在餐厅的餐饮支出。其结构如下:

现在,我们不再像之前那样将记录归类为“餐厅类别”,而是可以在“餐厅”账户上创建一条对应的记录,以记录用于餐饮的支出金额(79 美元):

“餐厅”账户与其他所有账户一样,也有一个累计余额,因此我们可以得知在“餐厅”上的总支出金额。这与统计支票账户的变动完全对称。
现在,我们可以通过创建一个包含这两条记录的“父级”框,将它们关联起来。我们将这个对象称为交易:

请注意,我们还为此交易添加了一个描述:“在 Uncle Boons 吃晚餐”。交易还包含一个日期,其所有记录均在此日期发生。我们称此为交易日期。
现在,我们可以引入复式记账法的基本规则:
The sum of all the postings of a transaction must equal zero.
请记住这一点,因为这是复式记账法的基础,也是其最重要的特征。它会带来重要影响,我将在本文后续部分进行讨论。
在我们的示例中,我们从‘支票账户’中扣除 79 美元,并‘转移’到‘餐厅账户’。(79 美元)+(-79 美元)= 0 美元。为了强调这一点,我可以在该笔交易的记账条目下方画一条求和线,如下所示:

多个账户
可能有许多这样的交易涉及多个不同的账户。例如,如果账户所有者第二天用信用卡在餐厅吃午饭,可以通过创建一个专门追踪实际信用卡余额的‘信用卡账户’来表示这笔交易,并生成相应的记账记录:

在这个示例中,所有者在一家名为‘Eataly’的餐厅消费了 35 美元。她之前信用卡的余额为-450 美元;消费后,新余额为-485 美元。
对于每一个现实中的账户,所有者都可以像我们这样创建一个会计账户。同时,对于每一类支出,所有者也会创建一个对应的会计账户。在这个系统中,账户的数量没有限制。
请注意,示例中的余额为负数;这并非错误。信用卡账户的余额通常为负数:它们代表你所欠的金额,即银行以信用方式借给你的资金。当你的信用卡公司记录你的消费时,他们会从自身角度出发,将这些金额记为正数。对你而言,这些是你最终需要偿还的款项。但在这里,我们的会计系统是从所有者角度出发的,对她来说,这是她所欠的钱,而不是她拥有的钱。我们实际拥有的,是一顿填满胃里的餐食(‘餐厅’账户中的正数金额)。
多笔记账
最后,一笔交易可以包含两个以上的记账项;事实上,它可以包含任意数量的记账项。唯一的要求是,这些记账金额的总和必须为零(根据上述复式记账规则)。
例如,让我们看看所有者收到 12 月工资时会发生什么:

本例中,她的税前工资记录为-2,905 美元(我稍后会解释符号的含义)。其中 905 美元被预留用于缴税。她的税后工资 2,000 美元(剩余部分)存入她的‘支票账户’,该账户的新余额为 2,921 美元(之前的余额 921 美元 + 2,000 美元 = 2,921 美元)。这笔交易包含三项记账:(+2,000) + (-2,905) + (+905) = 0。复式记账规则得到了遵守。
现在你可能会问:为什么她的工资被记录为负数?这里的逻辑与上述信用卡的例子类似,但可能更微妙一些。这些账户的设立是为了从所有者角度追踪所有金额。所有者提供劳动,换取金钱和税款(正数金额)。她所付出的劳动以美元为单位计量。这些劳动‘离开’了她(想象所有者口袋里储存着‘潜在劳动’,每天上班时,她将这些潜在劳动一点点撒给公司)。所有者‘付出’了价值 2,905 美元的劳动。我们希望追踪她付出了多少劳动,这正是通过‘工资’账户来实现的。这就是她的税前工资。
此外,为了简化起见,我们对这张工资单交易做了一些简化。更真实的工资单记录会包含更多账户:我们需要分别记录州税和联邦税金额,以及社会保障和医疗保险的扣款、通过工作支付的保险费用,以及该期间累计的带薪休假。但其复杂程度并不会增加太多:所有者只需将工资单上的所有金额转化为一笔包含更多分录的交易即可。整体结构保持相似。
账户类型
现在让我们关注所有者可能拥有的不同类型的账户。
余额或变动。 首先,账户之间最重要的区别在于:我们关心的是某个特定时间点的余额,还是只关心一段时间内的变动。例如,某人的支票账户或储蓄账户的余额是一个有意义的数值,无论是所有者还是银行都会关注。同样,某人信用卡的欠款总额也具有意义。住房抵押贷款的剩余应还金额也是如此。
另一方面,自某人出生以来在餐厅的总支出并不特别有意义。我们更关心的是在特定时间段内产生的餐厅支出金额。例如:“上个月你在餐厅花了多少钱?”或上个季度、去年。同样,自几年前开始在公司任职以来的总收入总额也不太重要;但我们关心的是在纳税年度内赚取的总额,因为这用于向税务机关申报收入。
-
在特定时间点余额具有意义的账户称为资产负债表账户。这类账户有两种:‘资产’和‘负债’。
-
另一类账户,即那些余额本身意义不大,但我们关心其在一段时间内变化的账户,称为利润表账户。同样,这类账户也有两种:‘收入’和‘支出’。
正常符号。 其次,我们考虑账户余额的常规符号。在复式记账系统中,绝大多数账户的余额通常保持正号或负号(尽管如前所述,账户余额符号发生变化并非不可能)。我们将以此区分前述的账户对:
-
对于资产负债表账户,资产通常为正余额,负债通常为负余额。
-
对于利润表账户,支出通常为正余额,收入账户通常为负余额。
这种情况总结如下表所示:
| 余额:正 (+) | 余额:负 (-) | |
|---|---|---|
余额在某个时间点有意义 (资产负债表) | 资产 | 负债 |
余额的变动在一段时间内有意义 (利润表) | 支出 | 收入 |
让我们逐一讨论每种账户类型,并提供一些示例,以免内容过于抽象。
-
资产。(+) 资产账户代表所有者拥有的东西。一个典型的例子是银行账户。另一个是“现金”账户,用于记录钱包里有多少现金。投资也属于资产(其单位不是美元,而是某种共同基金或股票的份额数量)。最后,如果你拥有房产,该房产本身也被视为资产(其市场价值会随时间波动)。
-
负债。(-) 负债账户代表所有者欠下的债务。最常见的例子是信用卡。尽管银行对账单上显示的是正数,但从你自己的角度看,它们是负数。贷款也是一种负债账户。例如,如果你为房产申请了抵押贷款,这笔钱是你欠下的,将由一个负数账户来记录。每月偿还抵押贷款时,这个负数会逐渐变大,也就是说,其绝对值随时间越来越小(例如:-120,000 → -117,345)。
-
支出。(+) 支出账户代表你所获得的东西,可能是通过用其他东西交换而取得的。这类账户看起来非常自然:食物、饮料、服装、房租、机票、酒店以及你通常用可支配收入购买的大多数其他类别。然而,税收通常也通过支出账户记录:当你获得薪资收入时,源扣税金额会立即作为支出记入账目。你可以将其理解为为全年享受的政府服务支付的费用。
-
收入。(-) 收入账户用于记录你为换取其他东西(通常是资产或支出)而付出的东西。对大多数有工作的人来说,那就是他们的时间价值(薪资收入)。具体而言,这里指的是毛收入。例如,如果你年薪为 120,000 美元,这个数字就是 120,000,而不是扣除税款后的净额。其他类型的收入包括投资分红或持有的债券利息。还有一些不太常见的收入项目,你也可以记录为收入,例如信用卡返现、他人给予的现金礼物等。
在 Beancount 中,所有账户名称都必须归属于前述四种账户类型之一。由于账户类型在其生命周期内不会改变,因此我们约定将其类型作为前缀附加到账户名称中。例如,餐厅账户的完整名称应为“Expenses:Restaurant”;银行支票账户的完整名称则为“Assets:Checking”。
除此之外,你可以自由为账户命名。你可以创建任意数量的账户,正如我们后面将看到的,还可以将它们组织成层级结构。截至本文撰写时,我使用了 700 多个账户来追踪我的个人财务事务。
现在让我们重新回顾之前的示例,并添加更多账户:

再假设还有更多交易:

……甚至还有更多交易:

最后,我们可以通过在账户名称前加上类型前缀,为每个账户标注其所属的四种账户类型之一:

一个记录个人所有事务的现实账本,每年可能包含数千笔交易。但其基本原则始终简单而一致:记账项会随着时间推移应用于账户,并且必须归属于某笔交易;在每笔交易中,所有记账项的总和必须为零。
当你为一组账户进行记账时,本质上是在描述所有账户在时间维度上发生的全部记账项,同时遵守上述规则。你正在创建一个这些记账项的数据库,即一本账簿。你正在“记账”——也就是说,传统上,这本账簿包含了所有这些交易。有些人称之为“维护日记账”。
现在,我们将关注如何从这些数据中获取有用的信息,对账簿中的信息进行汇总。
试算平衡
以我们之前的例子为例:我们可以轻松地重新排列所有账户,使所有资产账户集中排在顶部,接着是负债账户,然后是收入账户,最后是费用账户。我们只是改变了顺序,而未修改交易结构,目的是将同类账户归类在一起:

我们已将账户重新排序:资产账户位于顶部,其次是负债账户,然后是一些权益账户(我们刚刚引入,稍后将详细讨论),接着是收入账户,最后是费用账户位于底部。
如果我们汇总所有账户的记账项,并仅显示每个账户名称及其最终余额,就会得到一份我们称之为“试算平衡表”的报告。

这仅仅反映了每个账户在特定时间点的余额。由于所有账户的初始余额均为零,且每笔交易本身的余额也为零,因此我们可以确定,所有这些余额的总和必然为零。1 这是我们设定的约束条件的结果:每个记账项必须属于某笔交易,且每笔交易中的记账项必须相互抵消。
利润表
从交易列表中提取的一种常见有用信息,是特定时间段内利润表账户的变化摘要。这告诉我们在此期间赚取和支出的金额,两者的差额即为所获得的利润(或亏损)。我们称之为“净利润”。
为了生成此摘要,我们只需关注收入和费用账户的余额,仅汇总特定时间段内的交易,并将收入余额列在左侧,费用余额列在右侧:

请注意这里的符号:收入数值为负数,费用数值为正数。因此,如果你的收入高于支出(理想结果),收入与费用余额的总和将是一个负数。与其他收入一样,净利润为负数意味着存在相应金额的资产和/或负债为正数(这对你是有利的)。
利润表告诉我们特定时间段内发生了哪些变化。公司通常会每季度向投资者(如果是上市公司,也可能向公众)报告此信息,以展示其盈利情况。个人通常在年度报税表中报告此信息。
结转收入
请注意,在利润表中,仅对特定时间区间内的交易进行汇总。这使得我们可以计算出一年内所获得的所有收入。如果我们从账户创建之初开始汇总所有交易,则会得到自账户创建以来累计的总收入。
实现相同效果的更好方法是将收入和支出账户的余额清零。Beancount 将这种基本转换称为“清零2”。具体操作如下:
-
计算从账户创建之初到报告期开始时这些账户的余额。例如,如果你的账户创建于 2000 年,而你想生成 2016 年的利润表,则需将 2000 年至 2016 年 1 月 1 日之间的余额相加。
-
插入交易以清空这些余额,并将金额转移至其他非收入或非支出账户。例如,如果截至 2016 年 1 月 1 日,该餐厅支出账户的余额为 85,321 美元,则会插入一笔交易:餐厅账户记-85,321 美元,“以前年度收益”账户记+85,321 美元。这些交易的日期为 2016 年 1 月 1 日。包含这笔交易后,该账户在该日期的余额将归零——这正是我们想要的效果。
下图中以绿色显示的是为所有利润表账户插入的这些交易。现在,若对整个账本中截至期末的所有交易进行汇总,将仅得到 2016 年期间的变化,因为这些账户在该日期的余额已归零:

这就是 bean-query 命令行工具中“CLEAR”操作的语义。
(注意,另一种实现利润表账户相同效果的方法是仅对清零日期之后的交易进行分类和统计;但若同时报告利润表账户和资产负债表账户,则资产负债表账户的余额将不正确。)
权益
接收这些累计收入的账户称为“以前年度收益”。它属于第五种也是最后一种账户类型:权益。我们之前未讨论这种账户类型,因为它们通常仅用于汇总金额以生成报表,而所有者通常不会直接向此类账户记账;软件会自动完成,例如在清零净收益时。
“权益”账户类型用于汇总所有过往活动所隐含的净收益。关键在于,如果我们现在将资产、负债和权益账户合并列出,由于收入和支出账户已被清零,这些账户余额的总和应恰好为零。而将所有权益账户相加,便能清楚地告诉我们对实体的权益份额——换句话说,如果你用资产偿还了所有负债,企业还剩下多少价值。
请注意,权益账户的正常余额符号为负数。这并无特殊含义,只是因为它们用于抵消资产和负债;如果所有者拥有任何价值,该数值应为负数。(负的权益意味着正的净资产。)
Beancount 中常用的权益账户有几种:
-
以前年度收益 或 留存收益。用于存放从账户创建之初到报告期开始时所有收入与支出账户余额总和的账户。这正是上一节所提到的账户。
-
当前收益,也称为净利润。这是一个用于汇总报告期间内所有收入与支出余额的账户。这些余额通过在报告期结束时“结清”收入与支出账户来填入。
-
期初余额。一种用于抵消初始化账户时所用存款的权益账户。当我们将过去的交易历史截断,但仍需确保某个账户的余额从特定金额开始时,会使用此类账户。
再次说明:您无需自行定义或使用这些账户,因为它们是为汇总交易而自动生成的。通常,这些账户由上述结清过程自动填入,或通过 Pad 指令向“期初余额”权益账户填入以往的汇总余额。这些账户由软件自动创建和填充。我们将在后续章节中介绍它们的使用方式。
资产负债表
另一种汇总方式是列出每个账户的所有者资产与负债。这回答了问题:“钱在哪里?”理论上,我们只需关注资产和负债账户,并在报告中列出它们:

然而,实践中还常伴随另一个密切相关的问题,并通常同时回答:“在偿还所有债务后,我们还剩下多少?”这被称为净资产。
如果收入与支出账户已被清零,且所有余额均已转入权益账户,则净资产应等于所有权益账户余额之和。因此,在编制资产负债表时,通常先结清净利润,然后显示权益账户的余额。报告格式如下:

请注意,资产负债表可以针对任何时间点编制,只需截断特定日期之后的交易列表即可。资产负债表展示的是某一天的余额快照;而损益表展示的是两个日期之间这些余额的差额。
汇总
将过去交易的历史汇总为一笔等效存款很有用。例如,如果我们关注某个账户在 2016 年全年的交易,而该账户在 2016 年 1 月 1 日的余额为 450 美元,我们可以删除所有之前的交易,并用一笔新交易替代:在 2015 年 12 月 31 日存入 450 美元,资金来源为其他账户。
这个资金来源就是权益账户期初余额。首先,我们可以对所有资产和负债账户执行此操作(见蓝色交易):

然后删除所有早于期初日期的交易,以获得截断后的交易列表:

当我们专注于某一特定时间段内的交易时,这是一种非常有用的操作。
(这是一个实现细节:这些操作与 Beancount 的设计有关。与其为所有报告操作提供参数,Beancount 将所有报告流程简化为仅操作整个交易流;这样,我们可以将交易列表转换为仅包含我们想要报告的数据。在此情况下,汇总仅是一种转换操作,它接受完整的交易集并返回一个等效的截断交易流。随后,从该交易流中可生成一份排除过去交易的日记账。
从程序设计的角度来看,这很有吸引力,因为程序的唯一状态是一系列交易流,且永远不会被直接修改。它简单而健壮。
期间报告
现在我们知道,可以通过“清零”并仅查看收入与支出账户(即损益表)来生成一段时间内的变动报表。同时,我们也知道可以通过清零操作,在任何时间点生成资产、负债和权益的快照(即资产负债表)。
更一般地说,我们希望检查特定的时间段。这不仅意味着需要一份损益表,还需要两份资产负债表:一份是该时间段开始时的资产负债表,另一份是该时间段结束时的资产负债表。
为实现这一点,我们应用以下转换操作:
-
期初开账。 首先,我们在期初清零净利润,将所有以往的收入余额转入权益账户中的前期收益。然后,我们汇总至期初。我们将清零与汇总的组合称为“开账”。
-
期末结账。 我们还会截断报告期结束之后的所有交易。这一操作称为“结账”。
这些就是 bean-query shell 中“OPEN”和“CLOSE”操作的含义3。处理后的交易流应如下所示。
“结账”包含两个步骤。首先,我们移除所有在结账日期之后的交易:

我们可以处理这一交易流,生成该期间的损益表。
然后,我们在所需报表的结束日期再次清零,但这次将净利润清零至“权益:收益:本期”:

从这些交易中,我们生成期末的资产负债表。
以上总结了使用 Beancount 准备交易流以生成报表所需的操作,以及对这些报表类型的基本介绍。
会计科目表
新用户常常困惑于账户名称应详细到何种程度。例如,是否应将交易对手直接包含在账户名称中,如以下示例所示?
Expenses:Phone:Mobile:VerizonWireless
Assets:AccountsReceivable:Clients:AcmeInc
还是应使用更简单的名称,如以下示例,转而依赖“交易对手”、“标签”或其他元数据来对分录进行分组?
Expenses:Phone
Assets:AccountsReceivable
答案是:取决于你自己。这是一个主观的选择,纯属个人偏好。我个人倾向于适度扩展账户名称,创建较长且描述性强的名称;而其他人则更喜欢保持名称简洁,并使用标签对分录进行分组。有时甚至根本不需要对分录子集进行过滤。没有绝对正确的答案,这取决于你的具体需求。
需要注意的是,账户名称隐含地定义了一个层级结构。某些报表代码会将“:”分隔符解释为构建内存中的树形结构,从而允许你折叠某个节点的子账户,并计算父账户的汇总值。这可以视为另一种对分录进行分组的方式。
国家-机构惯例
对于我的资产、负债和收入账户,我总结出一种有效的命名规范:以账户所在国家的代码作为根节点,后接代表对应机构的简短字符串,再在其下为该机构中的特定账户赋予唯一名称。例如:
<type> : <country> : <institution> : <account>
例如,支票账户可以命名为“Assets:US:BofA:Checking”,其中“BofA”代表“美国银行”。信用卡账户可以将卡的类型名称作为账户名,如“Liabilities:US:Amex:Platinum”,如果你有多张卡片,这种命名方式会很有用。
我发现这种命名方案并不适用于支出账户,因为支出账户通常代表通用类别。对于这些账户,按类别分组似乎更合理,例如使用“Expenses:Food:Restaurant”而非仅用“Expenses:Restaurant”。
无论如何,Beancount 仅强制要求根账户,其余均为建议,该规范并未在软件中编码。你可以自由尝试,之后也可以通过处理文本文件轻松修改所有名称。更多实用指导,请参阅食谱。
贷方与借方
到目前为止,我们尚未讨论“贷方”和“借方”的概念。这是有意为之:Beancount 大幅摒弃了这些概念,因为这样能简化一切。我认为,与其学习借方和贷方术语并根据不同账户类别采取不同处理方式,不如直接记住:收入、负债和权益账户的余额通常为负数,并统一对待所有账户。无论如何,本节将解释这些概念。
如前几节所述,我们将“收入”、“负债”和“权益”账户视为通常具有负余额。这听起来可能有些奇怪:毕竟,没人会认为自己的税前工资是负数,信用卡账单或房贷账单也总是显示正数。这是因为,在我们的复式记账系统中,所有账户的余额都是从账户所有者的视角出发来衡量的。我们从这一视角出发保持符号的一致性,因为这样所有账户操作都变得简单直接:所有操作都只是简单的加法,所有账户均被同等对待。
相比之下,传统会计人员通常将所有账户余额记录为正数,并根据应用账户的类型对记账操作进行不同处理。每个账户应使用的符号完全由其类型决定:资产和支出账户是借方账户,而负债、权益和收入账户是贷方账户,需要进行符号调整。此外,向账户添加正金额称为“借记”,从账户中扣除则称为“贷记”。例如,参见这份外部文档,它几乎让我头大;这篇近期帖子则提供了更多细节。这种记账方式使一切变得远比必要更复杂。
这种方法的问题在于,交易过账金额的总和不再是一个简单的加法。例如,假设你正在创建一笔新交易,涉及两个资产账户、一个费用账户和一个收入账户,系统却提示存在 9.95 美元的不平衡错误。你盯着这条记录仔细查看:究竟是哪一笔过账金额过小?还是某一笔金额过大?又或者,是否需要添加一笔新的过账?如果是,该添加到借方账户还是贷方账户?要完成这些判断需要耗费大量脑力。一些复式记账软件试图通过为借方和贷方分别设置列,并仅允许用户在与账户类型对应的列中输入金额来解决这一问题。这在视觉上有所帮助,但为何不直接使用正负号呢?
此外,当你查看会计等式时,还必须考虑它们的符号。这使得对等式进行变换变得困难,原本简单的过账求和过程也变得复杂难懂。
在纯文本记账中,我们更愿意摒弃这种不便的负担。我们直接在所有地方使用加法,并牢记负债、权益和收入账户通常具有负余额。虽然这不符合常规,但更容易理解。如果需要生成仅显示正数的传统报表,我们可以在报表代码中4触发符号翻转,仅在输出时调整符号。
省点力气吧:把‘借方’和‘贷方’这些术语从脑子里彻底清除。
会计等式
结合前文所述,我们可以轻松地用带符号的形式表达会计等式。如果:
-
A = 所有资产过账的总和
-
L = 所有负债过账的总和
-
X = 所有费用过账的总和
-
I = 所有收入过账的总和
-
E = 所有权益过账的总和
那么我们可以得出:
A + L + E + X + I = 0
这源于以下事实:
sum(all postings) = 0
而这一结论又源于每个交易都必然总和为零(这是 Beancount 强制保证的):
for all transactions t, sum(postings of t) = 0
此外,收入和费用过账的总和即为净收益(NI):
NI = X + I
如果我们通过将收入结转至权益中的留存收益账户来调整权益,以反映净收益的全部影响,我们将得到更新后的权益值(E’):
E’ = E + NI = E + X + I
于是我们得到一个简化的会计等式:
A + L + E’ = 0
如果我们对贷方和借方的符号进行调整(见前文),并使所有总和均为正数,则会得到熟悉的会计等式:
Assets - Liabilities = Equity
正如你所见,始终直接相加数字要简单得多。
纯文本记账
好的,现在我们已经理解了这种方法及其理论上的作用。复式记账系统的目的是将现实中各种账户发生的交易,以统一的格式复制到一个系统中,并从中提取各种视图和报表。现在,让我们关注如何在实践中记录这些数据。
本文介绍 Beancount,其目的是“使用文本文件进行复式记账”。Beancount 实现了一个简单的语法解析器,允许您记录交易和分录。一个示例交易的语法如下所示:
2016-12-06 * "Biang!" "Dinner"
Liabilities:CreditCard -47.23 USD
Expenses:Restaurants
您可以在文件中编写许多这样的声明,Beancount 会读取该文件并在内存中创建相应的数据结构。
验证。 解析交易后,Beancount 还会验证复式记账规则:它检查您所有交易的分录总和是否为零。如果您出错并记录了一个余额非零的交易,系统将显示错误。
余额断言。 Beancount 允许您复制来自外部账户的余额,例如月度对账单上列出的余额。它会处理这些余额,并检查您的输入交易所产生的余额是否与声明的余额一致。这有助于您轻松发现和定位错误。
插件。 Beancount 允许您编写程序,以自动化和/或处理输入文件中的交易流。您可以通过编写直接处理交易流的代码来构建自定义功能。
查询与报表。 它提供了工具,用于处理这些交易流,生成本文前面讨论过的各类报表。
还有一些其他细节,例如 Beancount 允许您追踪成本基础并进行货币兑换,但以上就是其核心内容。
表格视角
几乎总是,用户在邮件列表中提出的问题,关于如何计算或追踪某个值,都可以通过将数据视为一个长列表来轻松解决,其中部分数据需要被过滤和聚合。如果您意识到我们最终所做的不过是计算这些分录的“总和”,而交易和分录的属性正是我们用于筛选子集的关键,那么一切都会变得非常简单。在绝大多数情况下,答案就是找到某种方式来区分分录以进行选择,例如通过账户名称、添加标签、使用元数据等。将这些数据视为一张表格,往往能带来深刻的启发。
想象您有两个表:一个包含每笔交易的字段,如日期和描述;另一个包含每条分录的字段,如账户、金额和币种,以及指向其父交易的引用。最简单的数据表示方式是连接这两个表,将父交易的值复制到每条分录中。
例如,这份 Beancount 输入:
2016-12-04 * "Christmas gift"
Liabilities:CreditCard -153.45 USD
Expenses:Gifts
2016-12-06 * "Biang!" "Dinner"
Liabilities:CreditCard -47.23 USD
Expenses:Restaurants
2016-12-07 * "Pouring Ribbons" "Drinks with friends"
Assets:Cash -25.00 USD
Expenses:Tips 4.00 USD
Expenses:Alcohol
可以渲染为如下表格:
| 日期 | 日期 | 收款人 | 说明 | 账户 | 金额 | 币种 |
|---|---|---|---|---|---|---|
| 2016-12-04 | * | 圣诞礼物 | 负债:信用卡 | -153.45 | USD | |
| 2016-12-04 | * | 圣诞礼物 | 支出:礼物 | 153.45 | USD | |
| 2016-12-06 | * | Biang! | 晚餐 | 负债:信用卡 | -47.23 | USD |
| 2016-12-06 | * | Biang! | 晚餐 | 支出:餐厅 | 47.23 | USD |
| 2016-12-07 | * | Pouring Ribbons | 与朋友饮酒 | 资产:现金 | -25.00 | USD |
| 2016-12-07 | * | Pouring Ribbons | 与朋友饮酒 | 支出:小费 | 4.00 | USD |
| 2016-12-07 | * | Pouring Ribbons | 与朋友饮酒 | 支出:酒精饮料 | 21.00 | USD |
请注意,交易字段的值在每个分录中都会重复。这完全类似于常规数据库的连接操作。分录字段从“账户”列开始。(另外请注意,此示例表格已简化;实际中包含更多字段。)
如果你有一个像这样的连接表,就可以对它进行筛选,并对任意分录组的金额进行求和。这正是 bean-query 工具允许你做的事情:你可以对等同于这个内存表的数据运行 SQL 查询,并列出如下值:
SELECT date, payee, number WHERE account = "Liabilities:CreditCard";
或像这样对头寸进行求和:
SELECT account, sum(position) GROUP BY account;
这个简单的最后一条命令生成了试算平衡表。
请注意,表格表示法本身并不强制要求分录总和为零。如果你的行筛选条件(WHERE 子句)始终选择每个匹配交易的所有分录,那么所有分录的最终总和必然为零。否则,总和可能为任意值。请记住这一点。
如果你熟悉 SQL 数据库,可能会问:为什么 Beancount 不直接处理其数据以填充现有的数据库系统,从而让用户使用这些数据库的工具?主要有两个原因:
-
报表操作。 为了生成损益表和资产负债表,交易列表需要使用前面描述的 clear、open 和 close 操作进行预处理。这些操作在数据库查询中难以实现,且依赖于具体的报表需求,理想情况下不应修改输入数据。我们必须将分录数据加载到内存中并运行一些代码。而我们已经在解析输入文件时完成了这一过程;数据库步骤将是多余的。
-
头寸聚合。 尽管本文尚未讨论,但账户内容可能包含不同类型的商品,以及附带成本基础的头寸。这些头寸的聚合方式需要自定义数据类型来实现,因为它遵循某些关于头寸如何相互抵消的规则(详见 库存如何工作)。在仅使用单一货币且忽略成本基础的 SQL 数据库中,实现这些操作将非常困难。
因此,Beancount 提供了一个自定义工具,直接处理和查询其数据:它提供了一个自己的 SQL 客户端实现,允许你指定 open 和 close 日期,并利用自定义的“库存”数据结构对分录头寸进行求和。该工具支持 Beancount 的核心类型列:Amount、Position 和 Inventory 对象。
(无论如何,如果你仍不认同,Beancount 提供了一个工具,可将其内容导出到常规 SQL 数据库系统。如果你愿意,欢迎自行尝试,尽情探索。)