跳转到主要内容

摊销

学习如何在 Beancount 中有效地管理摊销,让你能够将费用分摊到不同时期,从而实现更清晰的财务管理和规划。

将预付成本记录为资产,然后通过普通的 Beancount 交易将每月的份额释放为费用。下面的脚本会打印出完整、可复现的账目,涵盖保险、软件、聘用费和设备折旧。它只需要 Python 3,不需要任何第三方库。

例如,一份六个月的汽车保险保单预付 600 美元。每月释放 100 美元,可以将现金支付与费用确认区分开来,并帮助你有效地制定预算

什么是摊销?

摊销无形资产或预付项目的成本分摊到其受益的各个时期。这些示例使用简单的月度约定进行管理报告。它们并不决定税收扣除或法定折旧方法。

Beancount 解决方案:fava.plugins.amortize_over

兼容性检查于 2026-09-07: fava.plugins.amortize_over标准 Fava 1.30.16 的插件目录中不存在。将它的 plugin 指令添加到该标准安装中会产生导入错误。此处的可移植示例已在 Beancount 3.2.3、beanquery 0.2.0 和 Fava 1.30.16 上测试过。

Beancount.io 的自定义源码确实包含一个 amortize_over 实现。它需要一个单独的付款交易和一个恰好有两个过账条目且带有 amortize_months 元数据的计划交易。其月度日期遵循计划中的原始日期,并在加载时省略未来的副本。其舍入规则会将美分分配到剩余的各个时期。此源码证据并不能确定托管部署当前运行的是哪个版本。下面的显式计划有其自己的指定日期和最后一期的舍入规则。

第 1 步:启用插件

此工作流程无需启用任何插件。将第 3 步中的 Python 代码块保存为 amortize.py。安装 uv 以使用此处使用的版本来运行验证和查询命令。仅当你想要网页报告时才需要 Fava。

该脚本会将普通交易输出到标准输出。将该输出重定向到一个新的示例文件,对其进行检查和验证,然后再将条目整合到你的账簿中。使用 > 重新运行会替换该生成的示例文件;使用 >> 追加则会重复交易。

第 2 步:构建你的账户结构

每个生成的文件都是一个独立的示例账目。保险示例会开设以下账户,并在支付保费前提供一个说明性的 600 美元开户银行余额:

账户用途
Assets:Bank:Checking用于支付的现金
Assets:Prepaid:Insurance等待费用确认的保费
Expenses:Insurance:Auto每月保险费用
Equity:Opening-Balances示例起始现金的对应方

在将生成的交易调整到现有账目中时,请使用你实际的账户开设和银行余额。不要将示例的起始现金添加到已注入资金的账户中,也不要重复记录账簿中已有的购买。

第 3 步:记录摊销交易

将此完整程序保存为 amortize.py。每个场景都指定了付款日期、每月释放次数、购买资产、费用账户以及释放所贷记的账户。第一次释放是在付款日期;后续释放落在后续月份的同一日期,如有必要,会限制在月末。

import argparse
import calendar
from datetime import date
from decimal import Decimal, ROUND_HALF_EVEN
 
# amount, months, payment date, purchase asset, expense, release credit account
SCENARIOS = {
    "insurance": (
        "600.00", 6, "2024-06-01", "Assets:Prepaid:Insurance",
        "Expenses:Insurance:Auto", "Assets:Prepaid:Insurance",
    ),
    "software": (
        "1200.00", 12, "2024-01-15", "Assets:Prepaid:Software",
        "Expenses:Software:Adobe", "Assets:Prepaid:Software",
    ),
    "retainer": (
        "4500.00", 3, "2024-01-05", "Assets:Prepaid:Professional",
        "Expenses:Professional:Legal", "Assets:Prepaid:Professional",
    ),
    "equipment": (
        "3000.00", 36, "2024-01-20", "Assets:Equipment:Computers:Cost",
        "Expenses:Depreciation:Computers", "Assets:Equipment:Computers:AccumDep",
    ),
}
 
parser = argparse.ArgumentParser()
parser.add_argument("scenario", choices=SCENARIOS)
args = parser.parse_args()
amount, months, paid, asset, expense, credit = SCENARIOS[args.scenario]
total = Decimal(amount)
start = date.fromisoformat(paid)
monthly = (total / months).quantize(Decimal("0.01"), rounding=ROUND_HALF_EVEN)
bank = "Assets:Bank:Checking"
equity = "Equity:Opening-Balances"
 
 
def transaction(day, narration, debit, credit_account, value):
    print(f'\n{day} * "{narration}"')
    print(f"  {debit}  {value:.2f} USD")
    print(f"  {credit_account}  {-value:.2f} USD")
 
 
print('option "operating_currency" "USD"')
for account in sorted({bank, equity, asset, expense, credit}):
    print(f"2024-01-01 open {account} USD")
transaction("2024-01-01", "Example opening cash", bank, equity, total)
transaction(paid, f"Pay {args.scenario}", asset, bank, total)
 
for index in range(months):
    year, month_index = divmod(start.year * 12 + start.month - 1 + index, 12)
    month = month_index + 1
    day = min(start.day, calendar.monthrange(year, month)[1])
    release_date = date(year, month, day)
    # Put the entire rounding remainder into the final release exactly once.
    release = monthly if index < months - 1 else total - monthly * (months - 1)
    transaction(release_date, f"{args.scenario} release {index + 1}/{months}",
                expense, credit, release)

在包含 amortize.py 的目录下运行此命令块:

python3 amortize.py insurance > insurance.beancount
uvx --from beancount-io==0.1.0 bea --file insurance.beancount check

付款会贷记 Assets:Bank:Checking 账户 600.00 USD,并借记预付保险账户相同金额。每次释放会借记费用账户并贷记预付保险账户 100.00 USD共有六次释放,分别发生在 2024 年 6 月 1 日、7 月 1 日、8 月 1 日、9 月 1 日、10 月 1 日和 11 月 1 日。6 月释放后,预付余额为 500.00 USD,7 月释放后为 400.00 USD,11 月释放后为零。

实际示例

年度软件许可

使用相同的保存脚本生成一个独立的软件示例:

python3 amortize.py software > software.beancount
uvx --from beancount-io==0.1.0 bea --file software.beancount check

1,200 美元的许可付款日期为 2024 年 1 月 15 日。从 2024 年 1 月 15 日到 12 月 15 日,共释放 12 次,每次 100.00 USD。此示例在每个月初确认整整一个月的费用;它不对 1 月进行按比例分配。在最后一次释放后,Assets:Prepaid:Software 为零,Expenses:Software:Adobe 总计 1200.00 USD

专业服务聘用费

此独立示例将 1 月 5 日的 4,500 美元付款视为预付费用,并在 1 月、2 月和 3 月平均消耗:

python3 amortize.py retainer > retainer.beancount
uvx --from beancount-io==0.1.0 bea --file retainer.beancount check

三次释放,每次 1500.00 USD,日期分别为 2024 年 1 月 5 日、2 月 5 日和 3 月 5 日,最终使 Assets:Prepaid:Professional 为零,Expenses:Professional:Legal4500.00 USD。对于随着服务交付而赚取的聘用费,请用实际的服务时间表替换此假设。

设备折旧(固定资产)

固定资产折旧使用一个对照资产账户来保留购买成本。此示例假设使用寿命为三年,残值为零,并从 2024 年 1 月 20 日开始全额月度确认:

python3 amortize.py equipment > equipment.beancount
uvx --from beancount-io==0.1.0 bea --file equipment.beancount check

购买会一次性借记 Assets:Equipment:Computers:Cost 账户 3000.00 USD。36 次释放会借记 Expenses:Depreciation:Computers 并贷记 Assets:Equipment:Computers:AccumDep。第 1-35 次释放为 83.33 USD;第 36 次释放(2026 年 12 月 20 日)为 83.45 USD。因此 35 × 83.33 + 83.45 = 3000.00。如果全部 36 次都使用 83.33 USD,将会有 0.12 USD 未折旧。

完成时,成本仍为 3000.00 USD,累计折旧为 -3000.00 USD,其账面净值为零。在设备仍被记录时,对照资产账户本身不会归零。

报告与验证

1. 使用断言监控预付余额

Beancount 会在其日期的开始(即该日交易之前)检查余额。将下面这个上下文相关的断言块追加到生成的 insurance.beancount 文件中,然后重新运行其 bea check 命令:

2024-06-02 balance Assets:Prepaid:Insurance 500.00 USD
2024-07-02 balance Assets:Prepaid:Insurance 400.00 USD
2024-07-02 balance Expenses:Insurance:Auto 200.00 USD
2024-11-02 balance Assets:Prepaid:Insurance 0.00 USD
2024-11-02 balance Expenses:Insurance:Auto 600.00 USD

7 月 2 日位于 两次释放(6 月 1 日和 7 月 1 日)之后。在 7 月 2 日断言 500.00 USD 是错误的。这些断言需要生成的保险账目;它们不是独立的交易。

2. 使用 bea query 进行高级验证

使用生成的保险文件,此可执行命令将返回截至 7 月 1 日的累计余额,使用 7 月 2 日作为排他截止日期:

uvx --from beancount-io==0.1.0 bea --file insurance.beancount query "SELECT account, sum(position) WHERE date < 2024-07-02 AND account ~ '^(Assets:Prepaid:Insurance|Expenses:Insurance:Auto)$' GROUP BY account ORDER BY account"

预期 Assets:Prepaid:Insurance400.00 USDExpenses:Insurance:Auto200.00 USD。如果按日期分组,则会显示每天的变动,而不是累计余额。

要在测试过的本地 Fava 版本中查看任何生成的文件,请使用:

uv run --no-project --with beancount==3.2.3 --with beanquery==0.2.0 --with fava==1.30.16 fava insurance.beancount

打开 Fava 打印的本地 URL。有关配置,请参阅 Fava 选项参考。请明确选择报告期间:生成器会输出整个计划,包括未来日期,因此不受限制的总数将包含未来的释放。对于单独的规划文件,请参阅预测场景工作流程

限制与最终提示

  • 日期是明确的: 更改购买日期不会静默地延迟首次释放。在此脚本中,首次释放等于付款日期;如果服务稍后开始,请单独调整开始日期。
  • 只舍入一次: 脚本会将常规月度金额舍入到美分,然后将余数放在最后一次释放中。保留完整的生成计划以保留该调整。
  • 普通条目持续存在: 这些释放保存在生成的文件中。检查并记录它们一次。不要同时为相同的成本启用自动摊销,否则费用将被重复确认。
  • 选择会计计划: 这些是管理示例,不是特定司法管辖区的税收规则。对于不规则的收益或每日按比例分配,请计算并记录实际金额和日期。

来源:https://beancount.io/zh/docs/Tips/amortize