본문으로 건너뛰기

상각 (Amortization)

Beancount에서 상각을 효과적으로 관리하는 방법을 배워보세요. 비용을 기간에 걸쳐 분산하여 재무 명확성과 관리를 개선할 수 있습니다.

선불 비용을 자산으로 기록한 후, 일반적인 Beancount 거래를 통해 매월 해당 비용을 지출로 전환합니다. 아래 스크립트는 보험, 소프트웨어, 리테이너, 그리고 장비 감가상각에 대한 완전하고 재현 가능한 원장(ledger)을 출력합니다. Python 3만 필요하며 추가 라이브러리는 필요하지 않습니다.

예를 들어, 6개월 자동차 보험 정책이 선불로 $600이 든다고 가정해 봅시다. 매월 $100씩 6회에 걸쳐 지출을 인식하게 되면 현금 지불과 지출 인식을 분리하고 효과적인 예산 편성에 도움이 됩니다.

상각(Amortization)이란?

상각은 무형 자산 또는 선불 항목의 비용을 이익을 보는 기간에 걸쳐 분산하는 것입니다. 아래 예시들은 관리 보고를 위한 단순한 월별 조건을 사용합니다. 이는 세금 공제나 법정 감가상각 방법을 결정하지 않습니다.

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
uv run --no-project --with beancount==3.2.3 bean-check insurance.beancount

지불은 checking 계정을 600.00 USD 증가시키고 선불 보험을 같은 금액만큼 대변합니다. 각 분할은 지출 계정을 차변하고 선불 보험을 100.00 USD 감변합니다. 6회의 분할이 있으며, 2024년 6월 1일, 7월 1일, 8월 1일, 9월 1일, 10월 1일 및 11월 1일에 있습니다. 선불 잔액은 6월 분할 후 500.00 USD, 7월 후 400.00 USD, 11월 후 0이 됩니다.

실용적인 예시

연간 소프트웨어 라이선스

동일한 저장 스크립트를 사용하여 별도의 독립된 소프트웨어 예시를 생성합니다:

python3 amortize.py software > software.beancount
uv run --no-project --with beancount==3.2.3 bean-check software.beancount

$1,200 라이선스 지불은 2024년 1월 15일입니다. 100.00 USD의 12회 분할은 2024년 1월 15일부터 12월 15일까지 실행됩니다. 이 예시는 각 월 시작에 전체 한 달을 인식하며 1월을 비례 배분하지 않습니다. 마지막 분할 후, Assets:Prepaid:Software가 0이고 Expenses:Software:Adobe는 총 1200.00 USD입니다.

전문 서비스 리테이너

이 별도 예시는 $4,500의 1월 5일 지불을 1월, 2월, 3월에 균등하게 소비되는 선불 비용으로 취급합니다:

python3 amortize.py retainer > retainer.beancount
uv run --no-project --with beancount==3.2.3 bean-check retainer.beancount

2024년 1월 5일, 2월 5일, 3월 5일의 1500.00 USD의 3회 분할은 Assets:Prepaid:Professional을 0으로, Expenses:Professional:Legal4500.00 USD로 만듭니다. 서비스가 제공됨에 따라 리테이너가 발생한다면 이 가정을 실제 서비스 스케줄로 대체하세요.

장비 감가상각 (고정 자산)

고정 자산 감가상각은 구매 원가를 보존하기 위해 대체 자산 계정을 사용합니다. 이 예시는 3년 내용연수, 잔존가치 0, 그리고 2024년 1월 20일부터 전체 월 인식을 가정합니다:

python3 amortize.py equipment > equipment.beancount
uv run --no-project --with beancount==3.2.3 bean-check equipment.beancount

구매는 Assets:Equipment:Computers:Cost3000.00 USD만큼 한 번 차변합니다. 36회의 분할은 Expenses:Depreciation:Computers를 차감하고 Assets:Equipment:Computers:AccumDep을 대변합니다. 1–35회 분할은 83.33 USD이며, 2026년 12월 20일의 36번째 분할은 **83.45 USD**입니다. 따라서 35 × 83.33 + 83.45 = 3000.00이 됩니다. 모든 36회를 83.33 USD로 하면 0.12 USD가 미감가로 남습니다.

완료 시, 원가는 3000.00 USD, 누적 감가상각은 -3000.00 USD, 그리고 그 순 장부 가치는 0입니다. 묵장에 장비가 여전히 기록되어 있는 동안 contra-account 자체는 0으로 돌아가지 않습니다.

보고 및 검증

1. 선불 잔액 모니터링 (어설션 사용)

Beancount는 해당 날짜의 시작, 그날의 거래 전에 잔액을 확인합니다. 이 컨텍스트 어설션 블록을 생성된 insurance.beancount에 추가하고 bean-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. bean-query로 고급 검증

생성된 보험 파일로 이 실행 가능한 명령은 7월 1일까지의 누적 잔액을 7월 2일 제외했습니다:

uv run --no-project --with beancount==3.2.3 --with beanquery==0.2.0 bean-query insurance.beancount "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 USD, Expenses: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/ko/docs/Tips/amortize