程序员复式记账指南(下)

文接上篇 程序员复式记账指南(上),上篇文章的最后,我们安装了 Beancount 基本环境,并通过官方提供示例账本,对其有了一个初步的认识。

一、基础语法入门

对于怎么使用 Beancount 来编写我们的账本,Beancount 开源工具的作者提供了非常详尽的《说明文档》

1.1 第一个账本

下面我们使用一个上篇文章中老王的例子来说明如何使用 Beancount 来编写我们的第一个账本:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
1970-01-01 open Income:Sales CNY
1970-01-01 open Assets:Bank:Saving:ICBC CNY
1970-01-01 open Assets:Fixed CNY ;固定资产
1970-01-01 open Liabilities:CreditCard:ICBC CNY
1970-01-01 open Expenses:Food CNY

2023-01-01 * "xx公司" "购买设备"
Assets:Bank:Saving:ICBC -1,000.00 CNY
Assets:Fixed 1,000.00 CNY

2023-01-01 * "采购食材"
Liabilities:CreditCard:ICBC -500.00 CNY
Expenses:Food 500.00 CNY

2023-01-02 * "煎饼销售收入"
Income:Sales -1,000.00 CNY
Assets:Bank:Saving:ICBC 1,000.00 CNY

2023-01-02 * "质量问题食材退货"
Expenses:Food -100.00 CNY
Liabilities:CreditCard:ICBC 100.00 CNY
  • 首先需要新建一个纯文本文件,文件名就叫 laowang.bean 吧,然后选一个自己喜欢的文本编辑器来编写我们的账本内容;
  • 在纯文本文件里,首先需要使用 open 命令来设立账户,开始部分表示账户的开户日期,这里使用了1970-01-01做为账户的开户日期,一劳永逸的保证了所有账户相关的交易都在开户日期之后,不过这不是一个好的做法,后面章节中将提供一些关于账户开户日期的最佳实践,这里暂时先略过;
  • 账户设立好之后,我们就可以真正的开始记账了,交易格式如上所示,第一行的最前面是交易日期(格式:YYYY-MM-DD),日期后面的(*)号代表这是一笔已核对无误的交易,如果对该笔交易还存在疑问的话,可以将其改成 (!) 号。后期核对账目的时候,需要格外注意,接下来的用双引号括起来的分别代表「交易对方」(Payee)和「交易描述」(Narration)。其中 Payee 是可选的,如上示例中首行只存在一串双引号括起来的内容的交易,这部分内容表示的是 Narration。
  • Beancount 中账户分类:
    • Assets - 资产类账户:如现金、银行存款、基金、股票、借出账款等
    • Liabilities - 负债类账户:如信用卡、房贷、车贷、花呗、向他人借款等
    • Income - 收入类账户:工资、奖金等
    • Expenses - 费用类账户:购物、旅行、水电费用等
    • Equity - 权益类账户:开始记账之前已有的资产或者负债,一般用于初始化账户使用

到这里,一个简单的账本就编写好了,看起来平平无奇,接下来我们将使用 Beancount 提供工具集来初步感受一下 Beancount 的魅力

1.2 生成报表

1
2
3
4
5
6
7
(BEANCOUNT) bean-report laowang.bean balances
Assets:Bank:Saving:ICBC
Assets:Fixed 1000.00 CNY
Equity
Expenses:Food 400.00 CNY
Income:Sales -1000.00 CNY
Liabilities:CreditCard:ICBC -400.00 CNY

我们可以通过 bean-report 命令来生成各种报表,如上示例,我们通过 balances 子命令来查询各个账户的余额,通过查阅上面生成的报表,老王对自己的财务状况了如指掌:有价值 1,000 元的固定资产,采购食材总共花费了 400 元,煎饼摊销售收入 1,000 元,欠着银行 400 元。bean-report 命令还能生成很多其他的报表,如:「损益表」、「资产负债表」,这些报表对我们的个人财务管理非常有帮助。

当然,上面的命令行方式生成报表在使用上多少有点门槛,Beancount 作者贴心的为我们提供一个 Web UI 工具 Fava,安装好 Fava 之后,我们就可以通过 fava laowang.bean 命令来启动一个 web 服务,默认访问地址:http://localhost:5000/,这里贴两个通过官方示例数据生成的页面来感受一下

二、进阶使用

下面将分几个真实的使用场景来分别介绍一下 Beancount 的进阶用法,通过这些使用场景,我们可以很直观的感觉到 Beancount 和 「复式记账」能处理多么复杂的交易,Let’s go。

2.1 场景1: 发工资

作为一个纯粹的打工人,工资收入作为我们的主要收入来源,如何将其完整无误的记录下来就变得异常重要了,请看示例:

1
2
3
4
5
6
7
8
2023-01-10 * "Some Company" "💰工资2022-12"
Income:SomeCompany:Salary -20,000.00 CNY ;应发工资
Income:SomeCompany:Benefits -500.00 CNY ;节日福利
Income:SomeCompany:HousingFund -2,000.00 CNY ;住房公积金单位扣除
Assets:Government:HousingFund 4,000.00 CNY ;住房公积金缴纳
Expenses:Government:SocialSecurity 1,500.00 CNY ;社保缴纳
Expenses:Government:IncomeTax 3,000.00 CNY ;个税缴纳
Assets:Bank:Saving:ICBC 14,000.00 CNY ;实发工资

看上面的例子是不是觉得很直观?其实它很好的将我们工资条上的各项条目给体现出来了,如果是使用「单式记账」的话,我们得编写好几笔交易,而且极其容易出错,「复式记账」可以非常方便的在同一笔交易中体现资金在各个账户之间的流动。如上示例中,把工资、福利、住房公积金、社保、个税等各项金额都记录进来,以后能很方便地统计出每个月有多少工资是白拿的。

2.2 场景2: 记录房产

房产的记录分为两个部分,首先是购买房产,其次是追踪房产的当前市场估值。可以通过给房子人民币变种价格(估值价格),比如我的自住房是这样记账的

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
;创建房产货币
2018-06-01 commodity HOUSE.ABC
name: "房产名称"
2018-06-01 open Assets:Property:CS:ABC HOUSE.ABC

;房产购买
2018-06-01 * "XX地产" "房产名称"
Assets:Property:CS:ABC 1 HOUSE.ABC {2,000,000.00 CNY} ;房产购买价格
Assets:Bank:Saving:ICBC 600,000.00 CNY ;首付款
Liabilities:Bank:BOC:MortgageLoan 1,400,000.00 CNY ;抵押贷款

;房产价格
2018-06-01 price HOUSE.ABC 2,000,000.00 CNY ;买入成本
2018-06-01 price HOUSE.ABC 2,000,000.00 CNY.UNVEST ;估值价格
2023-01-17 price HOUSE.ABC 2,500,000.00 CNY.UNVEST ;估值价格

Beancount 没有预先定义任何货币,因此我们可以记录任何我们想记的东西,这里的关键在于创建了人民币 CNY 的变种货币(commodity) CNY.UNVEST,这样当以人民币 CNY 展示总资产的时候,不会让估值价格影响总资产的统计。但是可以在价格页面查看房子的估值

2.3 场景3: AA消费

1
2
3
4
5
6
7
8
2023-01-13 * "xx 饭店" "和小明吃饭"
Assets:VA:Wechat -500.00 CNY ;微信零钱付款
Expenses:Food:DiningOut 250.00 CNY ;AA我的一半
Assets:Receivables:Xiao-Ming 250.00 CNY ;AA小明的一半

2023-01-17 * "小明" "AA吃饭收款"
Assets:VA:Wechat 250.00 CNY
Assets:Receivables:Xiao-Ming -250.00 CNY

如上示例和小明 AA 吃饭一共花费 500 元,其中 250 元记入自己的费用账户,另外 250 元记入应收账户,几天后,小明通过微信转账归还 250 元。

2.4 场景4: 货币转换

1
2
3
4
5
2023-01-17 * "在免税店买东西"
Assets:Cash -200.00 USD ;现金支付
Liabilities:CreditCard:ICBC -650.00 CNY @@ 100.00 USD ;信用卡付款
Expenses:Clothing:Pants +150.00 USD
Expenses:Clothing:Shoes +150.00 USD

本例中,涉及到了合并付款和货币转换。信用卡被扣掉了 650 人民币,这其实是由 100 美元转换而来。在 Beancount 中使用 @@ 即可连接两种互相转换的货币(commodity)。

2.5 使用 DSL 进行复杂查询

fava 提供的 Web UI 已经能展现很多有用的财务报表,满足大部分用户的需求,如果用户需要进行一些更复杂的数据统计,比如「我经常去哪里加油」,则可以使用 bean-query 工具用 SQL 语句进行查询,详见 Beancount 作者的文档:Beancount – Query Language。这是一个用来统计我加油次数的例子:

三、一些最佳实践

笔者是从2022年9月开始使用 Beancount 来管理个人财务,在使用过程中也总结了一些最佳实践。以下内容说说我个人是怎么使用 Beancount 的,希望对你有所帮助

3.1 账本编辑器的选择

笔者目前是使用 VSCode 做为账本文件编辑器,加上 Lencerf/vscode-beancount 插件的配合,会自动为 *.bean 或者 *.beancount 文件加上语法高亮以及账户名自动补全,还可以实现金额数据自动对齐,具体配置方法请自己 Google 或者参考 Github 上作者提供的帮助文档。

3.2 账户开户日期

关于账户开户日期的设定,之前给出的示例有点偷懒,直接使用1970年1月1日做为账户开户日期。这里其实可以有些更具创意的选择

  • Expenses 账户可以使用自己的出生日期作为开户日期;
  • Income 账户可以按来源分类,如 Income:SomeCompany:SalaryIncome:OtherCompany:Salary 等,然后以入职公司的时间作为开户日期;
  • AssetsLiabilities 账户中的借/贷记卡,可以以在银行的开户日期作为 Beancount 账户的开户日期。

3.3 账本文件结构

随着交易的增多,我们的账本文件会越来越大,编辑起来会变得很不方便。Beancount 为我们提供了 include 的语法,可以让我们在一个 Beancount 文件里包含另外的文件。我的主账本文件 main.bean 里只有一些配置相关的条目,其他都是 include 的,具体结构参考如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
.
├── 2022
│   ├── 09.bean
│   ├── 10.bean
│   ├── 11.bean
│   ├── 12.bean
│   ├── __index.bean
│   ├── creditcard.bean
│   ├── event.bean
│   ├── forecast.bean
│   ├── income.bean
│   ├── loan.bean
│   └── transfer.bean
├── 2023
│   ├── 01.bean
│   ├── __index.bean
│   ├── forecast.bean
│   ├── income.bean
│   ├── insurance.bean
│   ├── loan.bean
│   └── transfer.bean
├── account
│   ├── __index.bean
│   ├── assets.bean
│   ├── equity.bean
│   ├── expenses.bean
│   ├── income.bean
│   └── liabilities.bean
├── commodity
│   ├── __index.bean
│   └── fund.bean
├── doc
│   └── __index.bean
└── main.bean

3.4 定期对账

我一般是在月末的时候核对一遍所有 AssetsLiabilities 账户的余额,在账本文件中使用 balance 命令,可以让 Beancount 自动帮我们核对账户余额与实际的是否相符,比如说我在2022年12月31通过网银查看到我的工商银行储蓄账户的余额是 10,000 元:

1
2023-01-01 balance Assets:Bank:Saving:ICBC 10,000.00 CNY

如上的语句告诉 Beancount,在 2023年1月1日 00:00:00 这个时间点,我的工商银行储蓄账户的余额是 10,000 元,如果 Beancount 通过这个时间点之前的交易记录计算出来的余额和这个值不相符的话,会直接报错。这样能尽可能的帮助我们缩小查错范围


程序员复式记账指南(下)
https://nerd4me.github.io/posts/858cde56.html
作者
nerd4me
发布于
2023年1月17日
许可协议