Last active
January 1, 2023 12:03
-
-
Save cdjk/0b8da9e2cc2dee5f3887ab5160970faa to your computer and use it in GitHub Desktop.
amortize_over beancount plugin
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import math | |
| from beancount.core.data import Transaction | |
| from beancount.core.amount import Amount | |
| from datetime import date | |
| from dateutils import relativedelta | |
| __plugins__ = ('amortize_over',) | |
| def amortize_over(entries, unused_options_map): | |
| """Repeat a transaction based on metadata. | |
| Args: | |
| entries: A list of directives. We're interested only in the Transaction instances. | |
| unused_options_map: A parser options dict. | |
| Returns: | |
| A list of entries and a list of errors. | |
| """ | |
| new_entries = [] | |
| errors = [] | |
| for entry in entries: | |
| if isinstance(entry, Transaction) and 'amortize_months' in entry.meta: | |
| new_entries.extend(amortize_transaction(entry)) | |
| else: | |
| # Always replicate the existing entries - unless 'amortize_months' | |
| # is in the metadata | |
| new_entries.append(entry) | |
| return new_entries, errors | |
| def split_amount(amount, periods): | |
| if periods == 1: | |
| return [ amount ] | |
| amount_this_period = amount/periods | |
| amount_this_period = amount_this_period.quantize(amount) | |
| return [ amount_this_period ] + split_amount(amount-amount_this_period, periods-1) | |
| def amortize_transaction(entry): | |
| if len(entry.postings) != 2: | |
| raise ValueError('Amortized transactions must have exactly two postings.') | |
| new_entries = [] | |
| original_postings = entry.postings | |
| periods = entry.meta['amortize_months'] | |
| amount = abs(entry.postings[0].units.number) | |
| currency = entry.postings[0].units.currency | |
| monthly_amounts = split_amount(amount, periods) | |
| for (n_month, monthly_number) in enumerate(monthly_amounts): | |
| new_postings = [] | |
| for posting in entry.postings: | |
| new_monthly_number = monthly_number | |
| if posting.units.number < 0: | |
| new_monthly_number = -monthly_number | |
| new_posting = posting._replace(units=Amount(number=new_monthly_number, | |
| currency=currency)) | |
| new_postings.append(new_posting) | |
| new_entry = entry._replace(postings=new_postings) | |
| new_entry = new_entry._replace(date=entry.date + relativedelta(months=n_month)) | |
| if new_entry.date <= date.today(): | |
| new_entries.append(new_entry) | |
| return new_entries |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
There is a small bug in your document: you said "Amortize ... over six months", but only specified
amortize_months: 3. You probably want to update the transaction description to "three months" instead.