用 Beancount 为团队记账

关于 Beancount 如何为团队记账的资料并不是很多,周末研究了两天,感觉有一点点收获,在这里做一下分享,如果对 Beancount 还不是很了解,可以先读一下入门系列文章:

团队目前的收入主要来源于苹果,而且收支账户用的是我个人的银行卡,我们就用这个现状来做例子,来讨论一下:如何通过使用 Beancount 在同一个账户下区分个人和团队的资产。团队在刚启动的时候没有启动资金来购买必需品,例如购买“苹果开发者凭证”,因此需要 beancount 能够正确记录出借资金,以及后续的归还操作。除此之外,还需要能够单独维护每个团队成员的账户,并且能够让团队成员随时查看自己的账户余额信息。下面用具体的例子来说明,满足这三个需求:

  • 资产区分
  • 借贷明晰
  • 团队成员随时查看自己的账目

最后再讨论一下如何用 Beancount 给团队成员发股票。

托管账户与会计账户

开始之前先看一下账本的基本结构:

teamcount
  ├── accounting
  │   ├── Alice
  │   ├── Bob
  │   ├── Carol
  │   ├── David
  │   ├── Edward
  │   ├── ledger.bean
  │   └── team
  ├── custody
  │   ├── ledger.bean
  │   ├── personal.bean
  │   └── team.bean
  ├── ledger.bean
  └── shares.bean

这里面借鉴了银行的记账方式,使用CusodyAccounting 两个账户来记账。Custody 即托管账户,它的主要目的是保证账户数字是对的,银行账户本质上就是一个托管账户,它能保证你存 500,再取 300,余额一定是 200,但是它不负责记录为什么存 500 和为什么取 300,用 Beancount 的术语来说,就是 Custody 的记录只有 Income 和 Expenses两类。与托管账户相反,Accounting 即会计账户,不但要记录这 500 为什么来,还要记录是从哪里来的,记录在哪个资产下面。我们平时用 Beancount 做的记录其实都是会计账户。

可以用一个简单的方法来区分 CustodyAccounting:在记录一笔交易时,如果银行账户余额会随着交易完成而有所变动,则记录在 Custody 目录下。反之,如果交易不会导致银行余额变化,则记录在 Accounting

资产区分

Beancount 的使用始于开设帐号,好的账户设计是成功的一半,假设我个人的美国银行帐号有 $5000 美金,团队账户初始资金为 $0:

2024-01-01 * "Initial Account"
  Assets:US:BofA:Joint:Personal               5000.00 USD
  Assets:US:BofA:Joint:Team                         0 USD
  Equity:Opening-Balances

这里我使用了 Joint 关联帐号,而没有给 Team 单独开独立的虚拟账户主要有两个原因:一是因为这两个帐号的资金就是在同一个银行账户里,同时有能做到很好的区分。比如:当产品有收入从苹果转账进来时,可以直接入账团队账户,个人支出走个人账户:

2024-01-05 * "Income from Apple"
  Income:Apple:ProductA                         -500 USD
  Assets:US:BofA:Joint:Team                      500 USD
2024-01-02 * "Netflex" "Purchase Subscription"
  Assets:US:BofA:Joint:Personal                  -9.9 USD
  Expenses:Netflex                                9.9 USD

二是这样用 beancount 的 balance 校验时,可以用一行同时校验两个账户的总和,而这个总和恰好应该等于银行账户的余额:

2024-01-06 balance Assets:US:BofA             5490.1 USD

比较上下两种校验的写法,可以看到这里使用了 beancount “父结点等于所有子节点之和”这一特性:

2024-01-06 balance Assets:US:BofA:Joint:Personal  4990.1 USD
2024-01-06 balance Assets:US:BofA:Joint:Team      500 USD

通过这样的账户设置,就可以将同一个银行账户中,原来“不分你我”的资金,清晰的区分成个人和团队资金。下面我们再来看看如何从个人账户借款给团队账户。

借款与还款

初期阶段,团队资金为 0,团队花费的一些花费需要向我的个人账户借款购买,比如:域名,开发者证书等。等团队有了收入,再把这笔借款还上。以团队购买“苹果开发者许可”为例,来看一下一个完整的借贷过程是如何在 Beancount 中记录的。

首先在我个人账户里记录一笔应收帐款,表示借给了团队 99 刀:

2024-01-15 * "Team" "Lend Money to Team"
  Assets:LA:Receivables:Profity                    99 USD
  Assets:US:BofA:Joint:Personal                   -99 USD

但是此时团队账户Assets:US:BofA:Joint:Team 账户里并没有钱,因此还要从团队账户的角度,记录一笔借款债务,并将借来的钱入库:

2024-01-15 * "Team" "Borrow Money From Personal"
  Liabilities:Team:Payable:Personal               -99 USD
  Assets:US:BofA:Joint:Team                        99 USD

然后就可以进行购买了:

2024-01-15 * "Apple" "Purchase Apple Developer License"
  Assets:US:BofA:Joint:Team                      -99 USD
  Expenses:Apple:License                          99 USD

这样一笔记录分别记两笔看起来很麻烦,能不能直接从我的账户转钱给团队账户呢?Beancount 是允许这么操作的,但是缺点是,直接转账在账目看不到借款记录,将来可能就会忘记归还。而如果用了这样标准的出借流程,我个人的资产 Assets 里面有一笔应收帐款 99 USD,同时团队债务 Liabilities 里面也有一笔 -99 USD 的应付账款,就很清晰的标明有一笔借款未还。

还款的流程刚好相反,你也是需要做两笔记录:

  1. 团队账户偿还借款债务:
2024-01-06 * "Personal" "Pay Back Borrowed Money"
  Assets:US:BofA:Joint:Team                       -99 USD
  Liabilities:Team:Payable:Personal                99 USD
  1. 个人账户确认收款入账
2024-01-06 * "LA" "Received Lend Money"
  Assets:Personal:Receivables:Team                -99 USD
  Assets:US:BofA:Joint:Personal                    99 USD

为了更严格的Custody 里面的每一笔交易,最终的结果都能和银行余额对上,可以使用插件 “beancount.plugins.mark_unverified”,强制每一笔交易之后都必须有一个 balance 的检查,否则这条 txn 会被打上 unverified 的 metadata,这个metadata 信息在使用 fava 时,可以在 journal 视图中被标记出来。

团队成员如何查看自己账目

首先假设有团队账户有一笔收入,将这笔收入入账到团队账户:

2024-01-05 * "Income from Apple"
  Income:Apple:ProductA                         -500 USD
  Assets:US:BofA:Joint:Team                      500 USD

这笔收入在归还购买开发许可的欠款之后,还剩 401 USD

2024-01-21 balance Assets:US:BofA:Joint:Team     401 USD

假设我们团队有 5 个人,每人分 80 USD,由于这个操作只是把资金分给团队成员的子账户上,并没有真的通过转账,打到团队成员的银行上,因此这个操作不会改变银行账户余额,所以这些记录放在 Accounting 这边。具体记录如下:

首先为每个团队成员在团队账户下创建一个子账户,然后从团队角度给员工发工资:

2024-01-25 * "Salary"
  Assets:US:BofA:Joint:Team                     -400 USD
  Expenses:Salary:Alice                           80 USD
  Expenses:Salary:BoB                             80 USD
  Expenses:Salary:Carol                           80 USD
  Expenses:Salary:David                           80 USD
  Expenses:Salary:Personal                        80 USD ;myself in the team

然后再以员工角度使用 Income 收工资:

2024-01-25 * "Salary"
  Assets:US:BofA:Joint:Team:Alice                 80 USD
  Income:Salary:Team                             -80 USD

这样记两笔虽然麻烦,但是的好处多多: 当这两本账目单独看的时候,都是完整的,符合 Beancount 记账规则的完整账目;而合起来看的时候,income 和 expenses 也都有相应的记录可以对账。

当员工需要把钱从虚拟账户里转到自己的账户里时,可以记录为 Expenses:

2024-01-30 * "Alice" "Transfer Money to her account"
  Assets:US:BofA:Joint:Team:Alice                -80 USD
  Expenses:Salary:Alice

如何让团队成员随时查看呢,这时候就要用到 github 了,在 Github 上为每个员在组织账户里创建一个私有 repo,用来放 beancount 文件,例如:example-team/alice,然后给该员工只读权限,最后也是最重要的一步,你把这个账户作为 submodule 克隆回到我们的总账目录下面。 这样员工可以随时查看他自己的账目,而我也可以在总账目下做完修改,通过提交 submodule 的修改,把账目同步出去即可。

发股票

在会计准则里,股票发行总量,会提现在 Equity 里面,以负数的形式来记录,比如说,我们 5 个人,股票均分,一个人 20%,Equity 应该减记股票 Share -100%。 由于 beancount 不支持百分比,因此就可以记录如下:

2024-01-01 * "Alice" "Team Share"
  Assets:US:BofA:Joint:Team:Alice                 20 SHARE
  Equity:Team:Share

加一条记录,确认一下股票没有超发:

2024-01-30 balance Equity:Team:Share            -100 SHARE

最后总结

Beancount 有很多灵活的特性,它不但可以为个人记账,还可以为团队记账。我们使用“资产类别:组件:组件:组件”的方式,来定义出不同的账户,包括个人账户,团队账户,以及团队成员账户,父结点等于所有子节点之和这一特性非常好用。同时利用“每个文件都是一个单独账本入口”的特性,让这些账户都可以独立浏览。

当然,灵活性也是有代价了,在借贷过程中,需要重复记录两条数据,才能使每条记录”件件又着落“。我们还利用beancount 的纯文本文件,配合 github 为每个人开设了独立的账本空间,实现了简单的权限控制。最后还用到多币种的特性,为股票发放做了记录。

可以在 GitHub 上找到完整的例子源码: https://github.com/kidylee/teamcount