Changeset - aa1f2ea35aed
[Not reviewed]
0 2 0
Brett Smith - 4 years ago 2020-08-18 05:28:37
brettcsmith@brettcsmith.org
balance_sheet: Balance.total() accepts multiple account names.

This simplifies the code to total all equity.
2 files changed with 13 insertions and 13 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/balance_sheet.py
Show inline comments
...
 
@@ -14,43 +14,45 @@
 
# You should have received a copy of the GNU Affero General Public License
 
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
 

	
 
import argparse
 
import collections
 
import datetime
 
import enum
 
import logging
 
import os
 
import sys
 

	
 
from decimal import Decimal
 
from pathlib import Path
 

	
 
from typing import (
 
    Any,
 
    Collection,
 
    Dict,
 
    Hashable,
 
    Iterable,
 
    Iterator,
 
    List,
 
    Mapping,
 
    NamedTuple,
 
    Optional,
 
    Sequence,
 
    TextIO,
 
    Tuple,
 
    Union,
 
)
 

	
 
import odf.table  # type:ignore[import]
 

	
 
from beancount.parser import printer as bc_printer
 

	
 
from . import core
 
from .. import books
 
from .. import cliutil
 
from .. import config as configmod
 
from .. import data
 
from .. import ranges
 

	
 
EQUITY_ACCOUNTS = frozenset(['Equity', 'Income', 'Expenses'])
 
PROGNAME = 'balance-sheet-report'
 
logger = logging.getLogger('conservancy_beancount.tools.balance_sheet')
...
 
@@ -114,41 +116,43 @@ class Balances:
 
            try:
 
                classification_s = account.meta['classification']
 
                if isinstance(classification_s, str):
 
                    classification = data.Account(classification_s)
 
                else:
 
                    raise TypeError()
 
            except (KeyError, TypeError):
 
                classification = account
 
            if account.root_part() == 'Expenses':
 
                post_type = post.meta.get('expense-type')
 
            else:
 
                post_type = None
 
            key = BalanceKey(account, classification, period, fund, post_type)
 
            self.balances[key] += post.at_cost()
 

	
 
    def total(self,
 
              account: Optional[str]=None,
 
              account: Union[None, str, Collection[str]]=None,
 
              classification: Optional[str]=None,
 
              period: int=Period.ANY,
 
              fund: int=Fund.ANY,
 
              post_type: Optional[str]=None,
 
    ) -> core.Balance:
 
        if isinstance(account, str):
 
            account = (account,)
 
        retval = core.MutableBalance()
 
        for key, balance in self.balances.items():
 
            if not (account is None or key.account.is_under(account)):
 
            if not (account is None or key.account.is_under(*account)):
 
                pass
 
            elif not (classification is None
 
                      or key.classification.is_under(classification)):
 
                pass
 
            elif not period & key.period:
 
                pass
 
            elif not fund & key.fund:
 
                pass
 
            elif not (post_type is None or post_type == key.post_type):
 
                pass
 
            else:
 
                retval += balance
 
        return retval
 

	
 
    def classifications(self,
 
                        account: str,
...
 
@@ -315,39 +319,35 @@ class Report(core.BaseODS[Sequence[None], None]):
 
                prior_liabilities += prior_bal
 
                period_liabilities += period_bal
 
        self.add_row(
 
            self.string_cell("Total Liabilities"),
 
            self.balance_cell(period_liabilities, stylename=self.style_totline),
 
            self.balance_cell(prior_liabilities, stylename=self.style_totline),
 
        )
 
        self.add_row()
 
        self.add_row()
 

	
 
        prior_net = core.MutableBalance()
 
        period_net = core.MutableBalance()
 
        self.add_row(self.string_cell("Net Assets", stylename=self.style_bold))
 
        self.add_row()
 
        for fund in [Fund.UNRESTRICTED, Fund.RESTRICTED]:
 
            preposition = "Without" if fund is Fund.UNRESTRICTED else "With"
 
            period_bal = -sum(
 
                (self.balances.total(account=account, fund=fund)
 
                 for account in EQUITY_ACCOUNTS), core.MutableBalance(),
 
            )
 
            prior_bal = period_bal + sum(
 
                (self.balances.total(account=account, fund=fund, period=Period.PERIOD)
 
                 for account in EQUITY_ACCOUNTS), core.MutableBalance(),
 
            period_bal = -self.balances.total(account=EQUITY_ACCOUNTS, fund=fund)
 
            prior_bal = period_bal + self.balances.total(
 
                account=EQUITY_ACCOUNTS, fund=fund, period=Period.PERIOD,
 
            )
 
            self.add_row(
 
                self.string_cell(f"{preposition} donor restrictions"),
 
                self.balance_cell(period_bal),
 
                self.balance_cell(prior_bal),
 
            )
 
            prior_net += prior_bal
 
            period_net += period_bal
 
        self.add_row(
 
            self.string_cell("Total Net Assets"),
 
            self.balance_cell(period_net, stylename=self.style_subtotline),
 
            self.balance_cell(prior_net, stylename=self.style_subtotline),
 
        )
 
        self.add_row()
 
        self.add_row(
 
            self.string_cell("Total Liabilities and Net Assets"),
...
 
@@ -483,34 +483,33 @@ class Report(core.BaseODS[Sequence[None], None]):
 

	
 
        totals[0] -= period_bal
 
        totals[2] -= period_bal
 
        totals[3] -= prior_bal
 
        self.add_row()
 
        self.add_row(
 
            self.string_cell("Change in Net Assets"),
 
            *(self.balance_cell(total) for total in totals),
 
        )
 

	
 
        for kwargs in bal_kwargs:
 
            if kwargs['period'] is Period.PERIOD:
 
                kwargs['period'] = Period.BEFORE_PERIOD
 
            else:
 
                kwargs['period'] = Period.OPENING
 
        beginnings = [
 
            -sum((self.balances.total(account=account, **kwargs)
 
                  for account in EQUITY_ACCOUNTS), core.MutableBalance())
 
            -self.balances.total(account=EQUITY_ACCOUNTS, **kwargs)
 
            for kwargs in bal_kwargs
 
        ]
 
        self.add_row()
 
        self.add_row(
 
            self.string_cell("Beginning Net Assets"),
 
            *(self.balance_cell(beg_bal) for beg_bal in beginnings),
 
        )
 

	
 
        self.add_row()
 
        self.add_row(
 
            self.string_cell("Ending Net Assets"),
 
            *(self.balance_cell(beg_bal + tot_bal, stylename=self.style_bottomline)
 
              for beg_bal, tot_bal in zip(beginnings, totals)),
 
        )
 

	
 
    def write_functional_expenses(self) -> None:
...
 
@@ -594,34 +593,33 @@ class Report(core.BaseODS[Sequence[None], None]):
 
                self.period_name,
 
            ], numbercolumnsspanned=col_count, stylename=self.style_header)
 
        )
 
        self.add_row()
 
        self.add_row(
 
            odf.table.TableCell(),
 
            self.string_cell(self.period_name, stylename=self.style_huline),
 
            self.string_cell(self.opening_name, stylename=self.style_huline),
 
        )
 
        self.add_row(self.string_cell(
 
            "Cash Flows from Operating Activities",
 
            stylename=self.style_bold,
 
        ))
 
        self.add_row()
 

	
 
        totals = [
 
            -sum((self.balances.total(account=account, **kwargs)
 
                  for account in EQUITY_ACCOUNTS), core.MutableBalance())
 
            -self.balances.total(account=EQUITY_ACCOUNTS, **kwargs)
 
            for kwargs in bal_kwargs
 
        ]
 
        self.add_row(
 
            self.string_cell("Change in Net Assets"),
 
            *(self.balance_cell(bal) for bal in totals),
 
        )
 
        self.add_row(self.string_cell(
 
            "(Increase) decrease in operating assets:",
 
        ))
 
        for text, classification in self.walk_classifications_by_account('Assets'):
 
            text_cell = self.string_cell(self.SPACE + text)
 
            if classification is None:
 
                self.add_row(text_cell)
 
            elif classification == self.C_CASH:
 
                continue
 
            else:
tests/test_reports_balance_sheet.py
Show inline comments
...
 
@@ -81,32 +81,34 @@ def income_expense_balances():
 
    ({'account': 'Income'}, -20),
 
    ({'account': 'Income:Nonexistent'}, None),
 
    ({'classification': 'Postage'}, 30),
 
    ({'classification': 'Services'}, 20),
 
    ({'classification': 'Nonexistent'}, None),
 
    ({'period': Period.PRIOR, 'account': 'Income'}, '-9.60'),
 
    ({'period': Period.PERIOD, 'account': 'Expenses'}, 26),
 
    ({'fund': Fund.RESTRICTED, 'account': 'Income'}, -10),
 
    ({'fund': Fund.UNRESTRICTED, 'account': 'Expenses'}, 25),
 
    ({'post_type': 'fundraising'}, 20),
 
    ({'post_type': 'management'}, 10),
 
    ({'post_type': 'Nonexistent'}, None),
 
    ({'period': Period.PRIOR, 'post_type': 'fundraising'}, '9.60'),
 
    ({'fund': Fund.RESTRICTED, 'post_type': 'program'}, 10),
 
    ({'period': Period.PRIOR, 'fund': Fund.RESTRICTED, 'post_type': 'program'}, '4.80'),
 
    ({'period': Period.PERIOD, 'fund': Fund.RESTRICTED, 'post_type': 'ΓΈ'}, None),
 
    ({'account': ('Income', 'Expenses')}, 30),
 
    ({'account': ('Income', 'Expenses'), 'fund': Fund.UNRESTRICTED}, 15),
 
])
 
def test_balance_total(income_expense_balances, kwargs, expected):
 
    actual = income_expense_balances.total(**kwargs)
 
    if expected is None:
 
        assert not actual
 
    else:
 
        assert actual == {'USD': testutil.Amount(expected)}
 

	
 
def run_main(arglist=[], config=None):
 
    if config is None:
 
        config = testutil.TestConfig(books_path=testutil.test_path('books/fund.beancount'))
 
    stdout = io.BytesIO()
 
    stderr = io.StringIO()
 
    retcode = balance_sheet.main(['-O', '-'] + arglist, stdout, stderr, config)
 
    stdout.seek(0)
 
    stderr.seek(0)
0 comments (0 inline, 0 general)