2020-06-25 14:51:37 +00:00
|
|
|
"""test_tools_opening_balances.py - Unit tests for opening balance generation"""
|
|
|
|
# Copyright © 2020 Brett Smith
|
2021-01-08 21:57:43 +00:00
|
|
|
# License: AGPLv3-or-later WITH Beancount-Plugin-Additional-Permission-1.0
|
2020-06-25 14:51:37 +00:00
|
|
|
#
|
2021-01-08 21:57:43 +00:00
|
|
|
# Full copyright and licensing details can be found at toplevel file
|
|
|
|
# LICENSE.txt in the repository.
|
2020-06-25 14:51:37 +00:00
|
|
|
|
|
|
|
import collections
|
|
|
|
import copy
|
|
|
|
import datetime
|
|
|
|
import io
|
|
|
|
|
|
|
|
import pytest
|
|
|
|
|
|
|
|
from . import testutil
|
|
|
|
|
|
|
|
from beancount import loader as bc_loader
|
|
|
|
from conservancy_beancount.tools import opening_balances as openbalmod
|
|
|
|
|
|
|
|
from decimal import Decimal
|
|
|
|
from typing import NamedTuple, Optional
|
|
|
|
|
|
|
|
A_CHECKING = 'Assets:Checking'
|
|
|
|
A_EUR = 'Assets:EUR'
|
|
|
|
A_PREPAID = 'Assets:Prepaid:Expenses'
|
|
|
|
A_RECEIVABLE = 'Assets:Receivable:Accounts'
|
|
|
|
A_RESTRICTED = 'Equity:Funds:Restricted'
|
|
|
|
A_UNRESTRICTED = 'Equity:Funds:Unrestricted'
|
|
|
|
A_CURRCONV = 'Equity:Realized:CurrencyConversion'
|
|
|
|
A_CREDITCARD = 'Liabilities:CreditCard'
|
|
|
|
A_PAYABLE = 'Liabilities:Payable:Accounts'
|
|
|
|
A_UNEARNED = 'Liabilities:UnearnedIncome'
|
|
|
|
|
|
|
|
class FlatPosting(NamedTuple):
|
|
|
|
account: str
|
|
|
|
units_number: Decimal
|
|
|
|
units_currency: str
|
|
|
|
cost_number: Optional[Decimal]
|
|
|
|
cost_currency: Optional[str]
|
|
|
|
cost_date: Optional[datetime.date]
|
|
|
|
project: Optional[str]
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
cost_s = f' {{{self.cost_number} {self.cost_currency}, {self.cost_date}}}'
|
|
|
|
if cost_s == ' {None None, None}':
|
|
|
|
cost_s = ''
|
|
|
|
return f'<{self.account} {self.units_number} {self.units_currency}{cost_s}>'
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def make(cls,
|
|
|
|
account,
|
|
|
|
units_number,
|
|
|
|
units_currency='USD',
|
|
|
|
cost_number=None,
|
|
|
|
cost_date=None,
|
|
|
|
project=None,
|
|
|
|
cost_currency=None,
|
|
|
|
):
|
|
|
|
if cost_number is not None:
|
|
|
|
cost_number = Decimal(cost_number)
|
|
|
|
if cost_currency is None:
|
|
|
|
cost_currency = 'USD'
|
|
|
|
if isinstance(cost_date, str):
|
|
|
|
cost_date = datetime.datetime.strptime(cost_date, '%Y-%m-%d').date()
|
|
|
|
return cls(account, Decimal(units_number), units_currency,
|
|
|
|
cost_number, cost_currency, cost_date, project)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_beancount(cls, posting):
|
|
|
|
units_number, units_currency = posting.units
|
|
|
|
if posting.cost is None:
|
|
|
|
cost_number = cost_currency = cost_date = None
|
|
|
|
else:
|
|
|
|
cost_number, cost_currency, cost_date, _ = posting.cost
|
|
|
|
try:
|
|
|
|
project = posting.meta['project']
|
|
|
|
except (AttributeError, KeyError):
|
|
|
|
project = None
|
|
|
|
return cls(posting.account, units_number, units_currency,
|
|
|
|
cost_number, cost_currency, cost_date, project)
|
|
|
|
|
|
|
|
@classmethod
|
|
|
|
def from_output(cls, output):
|
|
|
|
entries, _, _ = bc_loader.load_string(output.read())
|
|
|
|
return (cls.from_beancount(post) for post in entries[-1].postings)
|
|
|
|
|
|
|
|
|
|
|
|
def run_main(arglist, config=None):
|
|
|
|
if config is None:
|
|
|
|
config = testutil.TestConfig(
|
|
|
|
books_path=testutil.test_path('books/fund.beancount'),
|
|
|
|
)
|
|
|
|
output = io.StringIO()
|
|
|
|
errors = io.StringIO()
|
|
|
|
retcode = openbalmod.main(arglist, output, errors, config)
|
|
|
|
output.seek(0)
|
|
|
|
return retcode, output, errors
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('arg', ['2018', '2018-03-01'])
|
|
|
|
def test_2018_opening(arg):
|
|
|
|
retcode, output, errors = run_main([arg])
|
|
|
|
assert not errors.getvalue()
|
|
|
|
assert retcode == 0
|
|
|
|
assert list(FlatPosting.from_output(output)) == [
|
|
|
|
FlatPosting.make(A_CHECKING, 10000),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -3000, project='Alpha'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -2000, project='Bravo'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -1000, project='Charlie'),
|
|
|
|
FlatPosting.make(A_UNRESTRICTED, -4000, project='Conservancy'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('arg', ['2019', '2019-03-01'])
|
|
|
|
def test_2019_opening(arg):
|
|
|
|
retcode, output, errors = run_main([arg])
|
|
|
|
assert not errors.getvalue()
|
|
|
|
assert retcode == 0
|
|
|
|
assert list(FlatPosting.from_output(output)) == [
|
2020-06-28 01:36:52 +00:00
|
|
|
FlatPosting.make(A_CHECKING, '10050.40'),
|
2020-06-25 14:51:37 +00:00
|
|
|
FlatPosting.make(A_PREPAID, 20, project='Alpha'),
|
|
|
|
FlatPosting.make(A_RECEIVABLE, 32, 'EUR', '1.25', '2018-03-03', 'Conservancy'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -3060, project='Alpha'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -1980, project='Bravo'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -1000, project='Charlie'),
|
2020-06-28 01:36:52 +00:00
|
|
|
FlatPosting.make(A_RESTRICTED, '-.40', project='Delta'),
|
2020-06-25 14:51:37 +00:00
|
|
|
FlatPosting.make(A_UNRESTRICTED, -4036, project='Conservancy'),
|
|
|
|
FlatPosting.make(A_PAYABLE, -4, project='Conservancy'),
|
|
|
|
FlatPosting.make(A_UNEARNED, -30, project='Alpha'),
|
|
|
|
]
|
|
|
|
|
|
|
|
@pytest.mark.parametrize('arg', ['2020', '2020-12-31'])
|
|
|
|
def test_2020_opening(arg):
|
|
|
|
retcode, output, errors = run_main([arg])
|
|
|
|
assert not errors.getvalue()
|
|
|
|
assert retcode == 0
|
|
|
|
assert list(FlatPosting.from_output(output)) == [
|
2020-06-28 01:36:52 +00:00
|
|
|
FlatPosting.make(A_CHECKING, 10281),
|
2020-06-25 14:51:37 +00:00
|
|
|
FlatPosting.make(A_EUR, 32, 'EUR', '1.5', '2019-03-03'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -3064, project='Alpha'),
|
|
|
|
FlatPosting.make(A_RESTRICTED, -2180, project='Bravo'),
|
2020-08-21 16:05:23 +00:00
|
|
|
FlatPosting.make(A_RESTRICTED, -900, project='Charlie'),
|
2020-06-28 01:36:52 +00:00
|
|
|
FlatPosting.make(A_RESTRICTED, -5, project='Delta'),
|
2020-08-21 16:05:23 +00:00
|
|
|
FlatPosting.make(A_UNRESTRICTED, -4180, project='Conservancy'),
|
2020-06-25 14:51:37 +00:00
|
|
|
]
|