3. 第一个 Beancount 账本

3.第一个 Beancount 账本

在上一章中,我们花费了不少功夫,把 Beancount 的整个工具链安装到了我们的系统里。现在,是时候亲手搭建我们的第一座“财务大厦”了。别担心,这比听起来要简单得多。在 Beancount 的世界里,我们不需要复杂的图形界面,也不需要点击无数的按钮。我们的“大厦”就是一份纯文本文件,我们的“蓝图”就是 Beancount 的语法。

本章的目标非常明确:我们将从零开始,创建一个可以工作的 Beancount 账本,录入我们的第一笔交易,并通过工具验证其正确性,最后还能看到一个漂亮的 Web 报告。这就像学习一门新语言时,我们总是从“Hello, World!”开始一样。

Beancount 文件组织

在开始写内容之前,我们得先规划一下我们的工作空间。一个好的组织结构能让未来的维护工作轻松很多。虽然 Beancount 对文件组织没有强制要求,但遵循一些成熟的约定会让你受益匪浅。

一个常见的实践是创建一个专门的目录来存放所有与记账相关的文件。比如,我们可以在主目录下创建一个叫做 finances 的文件夹。

mkdir -p ~/finances
cd ~/finances

在这个目录里,我们通常会放置以下几类文件:

  1. 主账本文件 (Ledger File):这是核心,包含了所有的交易、账户定义等。通常以 .beancount.bean 结尾。对于个人财务,一个主文件通常就足够了。我们把它命名为 main.beancount

  2. 导入配置 (Importers):如果你需要从银行下载 CSV 或 PDF 文件并导入交易,你会编写一些 Python 脚本来自动识别和提取数据。这些脚本可以放在一个单独的文件里,比如 importers.py

  3. 文档目录 (Documents):为了将账目与现实世界的凭证关联起来,最好建立一个文档目录结构,用来存放下载的对账单、发票、收据等。一个很好的做法是让目录结构与你的账户命名结构相匹配。例如:

    documents/
    ├── Assets/
    │   └── US/
    │       └── BofA/
    │           └── Checking/
    │               └── 2023-10-01.statement.pdf
    └── Expenses/
        └── Food/
            └── Groceries/
                └── 2023-10-05.receipt.pdf
    

    这样,Beancount 的工具(如 bean-file)就能自动将下载的文件归档到正确的位置。

对于本章,我们只需要关注第一步:创建 main.beancount 文件。让我们开始吧:

touch main.beancount

现在,我们有了一个空的文本文件,准备向其中注入生命。

Open 指令的用法

在 Beancount 中,任何账户都不能凭空存在。在你向一个账户记录任何交易(无论是存入还是支出)之前,你必须先使用 open 指令声明这个账户的存在。这就像在银行开立一个新账户一样。

open 指令的语法非常直观:

YYYY-MM-DD open AccountName [ConstraintCurrency, ...]
  • YYYY-MM-DD:账户被“打开”的日期。这通常是该账户第一次出现交易的日期,或者更早。
  • open:指令类型。
  • AccountName:账户的完整名称。记住,账户名称必须遵循 AssetsLiabilitiesIncomeExpensesEquity 这五大类型之一作为开头,并且用冒号 : 分隔层级。例如:Assets:US:BofA:Checking
  • [ConstraintCurrency, ...]:这是一个可选的约束。你可以指定这个账户只接受特定的货币或商品。这是一个非常好的实践,可以防止你手误输错货币单位。例如,一个美元支票账户可以约束为只接受 USD

让我们来规划一下我们第一个账本需要哪些最基本的账户。假设我们是一个生活在美国的用户,手头有一些现金,还有一个银行支票账户。

  1. 一个现金账户:用来存放我们钱包里的现金。我们把它归类为 Assets(资产)。
  2. 一个支票账户:我们的银行存款。同样归类为 Assets
  3. 一个工资收入账户:用来记录我们收到的薪水。这属于 Income(收入)。
  4. 一个餐饮开销账户:用来记录我们吃饭的花费。这属于 Expenses(支出)。

现在,把这些账户定义写入我们刚刚创建的 main.beancount 文件中:

; --- Account Definitions ---

; 资产账户 (Assets)
2023-01-01 open Assets:Cash
2023-01-01 open Assets:US:BofA:Checking      USD

; 收入账户 (Income)
2023-01-01 open Income:Employer:Salary

; 支出账户 (Expenses)
2023-01-01 open Expenses:Food:Groceries

代码讲解

  • 我们使用了 ; 来添加注释,这能让我们的账本文件更易读。
  • 我们为所有账户选择了同一个开立日期 2023-01-01,这代表从这一天起,这些账户就存在了。
  • 我们为支票账户添加了货币约束 USD,这意味着如果未来不小心输入了 CAD 或其他货币,Beancount 就会报错提醒我们。

首个 Beancount 交易

有了账户,我们就可以开始记录生活中的财务活动了。交易(Transaction)是 Beancount 的核心,它记录了资金在不同账户之间的流动。

一个交易指令的基本结构如下:

YYYY-MM-DD <flag> "Payee" "Narration" [Tags] [Links]
  AccountName1  Amount1
  AccountName2  Amount2
  ...
  • YYYY-MM-DD:交易发生的日期。
  • <flag>:一个标志,通常是 *(表示已确认的交易)或 !(表示需要核实的交易)。
  • "Payee":收款方或付款方的名称,例如 "Whole Foods""ACME Corp"
  • "Narration":交易的描述,可以写得更详细一些。
  • AccountNameAmount:交易涉及的账户和金额。最关键的是,所有交易的金额加起来必须为零,这就是复式记账的核心原则。

让我们来记录我们的第一笔交易:我们从雇主那里收到了一笔薪水,并将其存入了我们的支票账户。

这笔交易有两个方面:

  1. 我们的支票账户(Assets:US:BofA:Checking)增加了 3000 USD
  2. 我们的工资收入(Income:Employer:Salary)也相应地增加了 3000 USD

在复式记账中,收入通常被记录为负数(因为它减少了你的净资产),而资产增加则为正数。所以,为了让总和为零,我们需要这样记录:

; --- Transactions ---

2023-01-15 * "ACME Corp" "January Salary"
  Assets:US:BofA:Checking      3000.00 USD
  Income:Employer:Salary      -3000.00 USD

现在,我们再记录一笔开销。假设我们在 2023-01-16 去超市花了 150.25 USD 买食物。这笔交易同样有两个方面:

  1. 我们的支票账户减少了 150.25 USD
  2. 我们的餐饮开销账户(Expenses:Food:Groceries)增加了 150.25 USD

在复式记账中,支出通常被记录为正数(因为它增加了你的总支出,减少了你的净资产)。所以,为了让总和为零,我们需要这样记录:

2023-01-16 * "Whole Foods" "Weekly Groceries"
  Expenses:Food:Groceries      150.25 USD
  Assets:US:BofA:Checking     -150.25 USD

把这两笔交易也添加到你的 main.beancount 文件中。现在,你的文件看起来应该像这样:

; --- Account Definitions ---

; 资产账户 (Assets)
2023-01-01 open Assets:Cash
2023-01-01 open Assets:US:BofA:Checking      USD

; 收入账户 (Income)
2023-01-01 open Income:Employer:Salary

; 支出账户 (Expenses)
2023-01-01 open Expenses:Food:Groceries


; --- Transactions ---

2023-01-15 * "ACME Corp" "January Salary"
  Assets:US:BofA:Checking      3000.00 USD
  Income:Employer:Salary      -3000.00 USD

2023-01-16 * "Whole Foods" "Weekly Groceries"
  Expenses:Food:Groceries      150.25 USD
  Assets:US:BofA:Checking     -150.25 USD

bean-check 验证工具

在我们生成任何报告之前,我们必须确保我们的账本在语法和逻辑上都是正确的。这就是 bean-check 工具的用武之地。它会读取你的账本文件,检查语法错误,验证所有交易是否平衡,并执行你可能配置的任何其他检查。

在你的 finances 目录下,运行以下命令:

bean-check main.beancount

如果一切顺利,这个命令将不会有任何输出,并且会立即返回到命令行提示符。这在 Unix 工具中被称为“无消息即是好消息”(no news is good news)。它默默地告诉你:“你的账本完美无瑕!”

现在,让我们故意制造一个错误来体验一下 bean-check 的威力。假设我们在第二笔交易中不小心把金额输错了,导致交易不平衡:

2023-01-16 * "Whole Foods" "Weekly Groceries"
  Expenses:Food:Groceries      150.25 USD
  Assets:US:BofA:Checking     -150.00 USD  ; <-- 错误!

再次运行 bean-check

bean-check main.beancount

这次,你会看到类似下面的错误输出:

main.beancount:18:     Transaction does not balance: 150.25 USD

   2023-01-16 * "Whole Foods" "Weekly Groceries"
     Expenses:Food:Groceries      150.25 USD
     Assets:US:BofA:Checking     -150.00 USD

这个错误信息非常清晰:

  1. 它指出了错误所在的文件和行号 (main.beancount:18)。
  2. 它解释了错误原因:Transaction does not balance(交易不平衡)。
  3. 它还把有问题的交易内容打印了出来,方便你定位和修改。

这就是 Beancount 强大的数据校验能力。它能帮你捕捉到那些因粗心导致的小错误,确保你的财务数据始终是准确可靠的。现在,请把错误修正回来,让 bean-check 再次安静地通过。

初始 Web 界面访问

虽然命令行工具非常高效,但有时候我们更喜欢一个可视化的界面来浏览我们的财务状况。Beancount 自带了一个简单的 Web 服务器,可以将你的账本数据渲染成一系列 HTML 报告。

启动 Web 服务器同样非常简单:

bean-web main.beancount

运行这个命令后,你会看到类似下面的输出:

Serving HTTP on 0.0.0.0 port 8080 (http://0.0.0.0:8080/) ...

现在,打开你的网络浏览器(如 Chrome, Firefox 等),访问地址 http://localhost:8080

你会看到一个简洁的 Web 界面。左侧是导航栏,列出了各种报告,如:

  • Balance Sheet (资产负债表):显示你在某个时间点的资产、负债和净值。
  • Income Statement (损益表):显示在一段时间内的收入和支出。
  • Journal (日记账):显示所有账户的交易流水。
  • Holdings (持仓):如果你有投资,这里会显示你的持仓情况。

点击 "Balance Sheet",你会看到你的资产(Assets:US:BofA:Checking)和负债(目前没有)情况,以及净值(Equity)。点击 "Income Statement",你会看到你的收入(Income:Employer:Salary)和支出(Expenses:Food:Groceries)以及它们的差额(即你的储蓄)。

这个 Web 界面是你探索和分析你的财务数据的绝佳起点。当你修改 main.beancount 文件并保存后,只需在浏览器中刷新页面,就能看到最新的数据。

注意bean-web 是一个开发和学习工具。对于生产环境或需要与他人分享报告的场景,社区中有一个更强大、功能更丰富的 Web 界面叫做 Fava,我们将在后续章节中介绍。

首份报告生成

除了 Web 界面,我们还可以使用 bean-report 命令行工具来生成特定格式的报告,这在脚本自动化或快速查看数据时非常有用。

让我们生成几份基础报告:

  1. 账户余额报告 (Balances):这是一个最简单的报告,列出了所有账户的当前余额。

    bean-report main.beancount balances
    

    输出会是这样:

    Assets:Cash                                        0.00 USD
    Assets:US:BofA:Checking                         2849.75 USD
    Expenses:Food:Groceries                          150.25 USD
    Income:Employer:Salary                          -3000.00 USD
    

    这个报告清晰地展示了每个账户的状况:支票账户有 2849.75 USD,我们花了 150.25 USD 在食物上,我们收到了 3000 USD 的薪水。

  2. 资产负债表 (Balance Sheet):这是一个更正式的报告,将账户按类型分类。

    bean-report main.beancount balsheet
    

    它会以文本格式输出一个类似表格的结构,清晰地分离了资产、负债和权益。

  3. 损益表 (Income Statement):这个报告总结了特定时期内的收入和支出。

    bean-report main.beancount income
    

    输出会显示你的总收入、总支出,以及最重要的——净利润 (Net Income),也就是你存下来的钱。

通过这些简单的命令,我们已经能够从我们创建的纯文本文件中提取出有意义的财务摘要了。这就是 Beancount 的力量所在:数据与展示分离,让你可以随心所欲地以各种形式查看你的财务状况。


至此,我们已经成功地走完了创建一个 Beancount 账本的完整流程。我们学习了如何规划文件结构,如何使用 open 指令来声明账户,如何记录第一笔复式交易,如何使用 bean-check 来保证数据质量,以及如何通过 Web 界面和命令行报告来审视我们的财务成果。

你已经掌握了 Beancount 的“Hello, World!”。虽然这只是最基础的一步,但它为你打开了一扇通往清晰、可控的个人财务管理世界的大门。在下一章,我们将深入探讨复式记账的核心原理,理解为什么 Beancount 要如此严格地要求交易平衡,以及账户类型背后的会计逻辑。这将帮助你不仅“知其然”,更能“知其所以然”。