Changeset - 9ae974009b13
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-06-27 20:31:27
brettcsmith@brettcsmith.org
fund: Add outstanding balances to text fund report.
2 files changed with 60 insertions and 33 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/fund.py
Show inline comments
...
 
@@ -81,5 +81,11 @@ AccountsMap = Mapping[data.Account, core.PeriodPostings]
 
FundPosts = Tuple[MetaValue, AccountsMap]
 

	
 
BALANCE_ACCOUNTS = ['Equity', 'Income', 'Expenses']
 
EQUITY_ACCOUNTS = ['Equity', 'Income', 'Expenses']
 
INFO_ACCOUNTS = [
 
    'Assets:Receivable',
 
    'Assets:Prepaid',
 
    'Liabilities:Payable',
 
    'Liabilities:UnearnedIncome',
 
]
 
PROGNAME = 'fund-report'
 
UNRESTRICTED_FUND = 'Conservancy'
...
 
@@ -171,5 +177,5 @@ class TextReport:
 
    ) -> Iterator[Tuple[str, Sequence[str]]]:
 
        total_fmt = f'{fund} balance as of {{}}'
 
        for acct_s, balance in core.account_balances(account_map, BALANCE_ACCOUNTS):
 
        for acct_s, balance in core.account_balances(account_map, EQUITY_ACCOUNTS):
 
            if acct_s is core.OPENING_BALANCE_NAME:
 
                acct_s = total_fmt.format(self.start_date.isoformat())
...
 
@@ -177,4 +183,9 @@ class TextReport:
 
                acct_s = total_fmt.format(self.stop_date.isoformat())
 
            yield acct_s, (-balance).format(None, sep='\0').split('\0')
 
        for _, account in core.sort_and_filter_accounts(account_map, INFO_ACCOUNTS):
 
            balance = account_map[account].stop_bal
 
            if not balance.is_zero():
 
                balance = core.normalize_amount_func(account)(balance)
 
                yield account, balance.format(None, sep='\0').split('\0')
 

	
 
    def write(self, rows: Iterable[FundPosts]) -> None:
...
 
@@ -320,5 +331,4 @@ def main(arglist: Optional[Sequence[str]]=None,
 
        for post in data.Posting.from_entries(entries)
 
        if post.meta.date < args.stop_date
 
        and post.account.is_under(*BALANCE_ACCOUNTS)
 
    )
 
    for search_term in args.search_terms:
tests/test_reports_fund.py
Show inline comments
...
 
@@ -41,4 +41,6 @@ MID_DATE = datetime.date(2019, 3, 1)
 
STOP_DATE = datetime.date(2020, 3, 1)
 

	
 
EQUITY_ROOT_ACCOUNTS = ('Expenses:', 'Equity:', 'Income:')
 

	
 
OPENING_BALANCES = {
 
    'Alpha': 3000,
...
 
@@ -52,4 +54,6 @@ BALANCES_BY_YEAR = {
 
        ('Income:Other', 40),
 
        ('Expenses:Other', -4),
 
        ('Assets:Receivable:Accounts', 40),
 
        ('Liabilities:Payable:Accounts', 4),
 
    ],
 
    ('Conservancy', 2019): [
...
 
@@ -57,11 +61,17 @@ BALANCES_BY_YEAR = {
 
        ('Expenses:Other', Decimal('-4.20')),
 
        ('Equity:Realized:CurrencyConversion', Decimal('6.20')),
 
        ('Assets:Receivable:Accounts', -40),
 
        ('Liabilities:Payable:Accounts', -4),
 
    ],
 
    ('Alpha', 2018): [
 
        ('Income:Other', 60),
 
        ('Liabilities:UnearnedIncome', 30),
 
        ('Assets:Prepaid:Expenses', 20),
 
    ],
 
    ('Alpha', 2019): [
 
        ('Income:Other', 30),
 
        ('Expenses:Other', -26),
 
        ('Assets:Prepaid:Expenses', -20),
 
        ('Liabilities:UnearnedIncome', -30),
 
    ],
 
    ('Bravo', 2018): [
...
 
@@ -77,12 +87,4 @@ def fund_entries():
 
    return copy.deepcopy(_ledger_load[0])
 

	
 
def fund_postings(entries, project, stop_date):
 
    return (
 
        post for post in data.Posting.from_entries(entries)
 
        if post.meta.date < stop_date
 
        and post.account.is_under('Equity', 'Income', 'Expenses')
 
        and post.meta.get('project') == project
 
    )
 

	
 
def split_text_lines(output):
 
    for line in output:
...
 
@@ -95,4 +97,15 @@ def format_amount(amount, currency='USD'):
 
    )
 

	
 
def check_text_balances(actual, expected, *expect_accounts):
 
    balance = Decimal()
 
    for expect_account in expect_accounts:
 
        expect_amount = expected[expect_account]
 
        if expect_amount:
 
            actual_account, actual_amount = next(actual)
 
            assert actual_account == expect_account
 
            assert actual_amount == format_amount(expect_amount)
 
            balance += expect_amount
 
    return balance
 

	
 
def check_text_report(output, project, start_date, stop_date):
 
    _, _, project = project.rpartition('=')
...
 
@@ -106,9 +119,8 @@ def check_text_report(output, project, start_date, stop_date):
 
        else:
 
            for account, amount in amounts:
 
                if year < start_date.year:
 
                if year < start_date.year and account.startswith(EQUITY_ROOT_ACCOUNTS):
 
                    balance_amount += amount
 
                else:
 
                    expected[account] += amount
 
    expected.default_factory = None
 
    actual = split_text_lines(output)
 
    next(actual); next(actual)  # Discard headers
...
 
@@ -118,18 +130,10 @@ def check_text_report(output, project, start_date, stop_date):
 
    )
 
    assert open_amt == format_amount(balance_amount)
 
    for expect_account in [
 
            'Equity:Realized:CurrencyConversion',
 
            'Income:Other',
 
            'Expenses:Other',
 
    ]:
 
        try:
 
            expect_amount = expected[expect_account]
 
        except KeyError:
 
            continue
 
        else:
 
            actual_account, actual_amount = next(actual)
 
            assert actual_account == expect_account
 
            assert actual_amount == format_amount(expect_amount)
 
            balance_amount += expect_amount
 
    balance_amount += check_text_balances(
 
        actual, expected,
 
        'Equity:Realized:CurrencyConversion',
 
        'Income:Other',
 
        'Expenses:Other',
 
    )
 
    end_acct, end_amt = next(actual)
 
    assert end_acct == "{} balance as of {}".format(
...
 
@@ -137,4 +141,11 @@ def check_text_report(output, project, start_date, stop_date):
 
    )
 
    assert end_amt == format_amount(balance_amount)
 
    balance_amount += check_text_balances(
 
        actual, expected,
 
        'Assets:Receivable:Accounts',
 
        'Assets:Prepaid:Expenses',
 
        'Liabilities:Payable:Accounts',
 
        'Liabilities:UnearnedIncome',
 
    )
 
    assert next(actual, None) is None
 

	
...
 
@@ -144,5 +155,9 @@ def check_ods_report(ods, start_date, stop_date):
 
        'Income': Decimal(0),
 
        'Expenses': Decimal(0),
 
        'Equity': Decimal(0),
 
        'Equity:Realized': Decimal(0),
 
        'Assets:Receivable': Decimal(0),
 
        'Assets:Prepaid': Decimal(0),
 
        'Liabilities:Payable': Decimal(0),
 
        'Liabilities': Decimal(0),  # UnearnedIncome
 
    }) for key, amount in sorted(OPENING_BALANCES.items()))
 
    for fund, year in itertools.product(account_bals, range(2018, stop_date.year)):
...
 
@@ -153,8 +168,8 @@ def check_ods_report(ods, start_date, stop_date):
 
        else:
 
            for account, amount in amounts:
 
                if year < start_date.year:
 
                if year < start_date.year and account.startswith(EQUITY_ROOT_ACCOUNTS):
 
                    acct_key = 'opening'
 
                else:
 
                    acct_key, _, _ = account.partition(':')
 
                    acct_key, _, _ = account.rpartition(':')
 
                account_bals[fund][acct_key] += amount
 
    account_bals['Unrestricted'] = account_bals.pop('Conservancy')
...
 
@@ -170,9 +185,11 @@ def check_ods_report(ods, start_date, stop_date):
 
            assert next(cells).value == balances['Income']
 
            assert next(cells).value == -balances['Expenses']
 
            if balances['Equity']:
 
                assert next(cells).value == balances['Equity']
 
            if balances['Equity:Realized']:
 
                assert next(cells).value == balances['Equity:Realized']
 
            else:
 
                assert not next(cells).value
 
            assert next(cells).value == sum(balances.values())
 
            assert next(cells).value == sum(balances[key] for key in [
 
                'opening', 'Income', 'Expenses', 'Equity:Realized',
 
            ])
 
    assert not account_bals, "did not see all funds in report"
 

	
0 comments (0 inline, 0 general)