balance_sheet: Add cash flows report.

With this, the balance sheet report has all the reports from the audit.
This commit is contained in:
Brett Smith 2020-08-17 17:09:31 -04:00
parent eaaf8fe98c
commit 07757e7717
2 changed files with 112 additions and 5 deletions

View file

@ -181,9 +181,11 @@ class Balances:
class Report(core.BaseODS[Sequence[None], None]): class Report(core.BaseODS[Sequence[None], None]):
C_CASH = 'Cash'
C_SATISFIED = 'Satisfaction of program restrictions' C_SATISFIED = 'Satisfaction of program restrictions'
EQUITY_ACCOUNTS = ['Equity', 'Income', 'Expenses'] EQUITY_ACCOUNTS = frozenset(['Equity', 'Income', 'Expenses'])
NO_BALANCE = core.Balance() NO_BALANCE = core.Balance()
SPACE = ' ' * 4
def __init__(self, def __init__(self,
balances: Balances, balances: Balances,
@ -219,6 +221,7 @@ class Report(core.BaseODS[Sequence[None], None]):
self.write_financial_position() self.write_financial_position()
self.write_activities() self.write_activities()
self.write_functional_expenses() self.write_functional_expenses()
self.write_cash_flows()
def walk_classifications(self, cseq: Iterable[data.Account]) \ def walk_classifications(self, cseq: Iterable[data.Account]) \
-> Iterator[Tuple[str, Optional[data.Account]]]: -> Iterator[Tuple[str, Optional[data.Account]]]:
@ -226,11 +229,11 @@ class Report(core.BaseODS[Sequence[None], None]):
for classification in cseq: for classification in cseq:
parts = classification.split(':') parts = classification.split(':')
tail = parts.pop() tail = parts.pop()
tabs = ' ' * 4 * len(parts) space = self.SPACE * len(parts)
if parts != last_prefix: if parts != last_prefix:
yield f'{tabs[4:]}{parts[-1]}', None yield f'{space[len(self.SPACE):]}{parts[-1]}', None
last_prefix = parts last_prefix = parts
yield f'{tabs}{tail}', classification yield f'{space}{tail}', classification
def walk_classifications_by_account( def walk_classifications_by_account(
self, self,
@ -549,6 +552,7 @@ class Report(core.BaseODS[Sequence[None], None]):
for text, classification in self.walk_classifications_by_account('Expenses'): for text, classification in self.walk_classifications_by_account('Expenses'):
text_cell = self.string_cell(text) text_cell = self.string_cell(text)
if classification is None: if classification is None:
self.add_row(text_cell)
if not text[0].isspace(): if not text[0].isspace():
self.add_row() self.add_row()
self.add_row(text_cell) self.add_row(text_cell)
@ -576,6 +580,109 @@ class Report(core.BaseODS[Sequence[None], None]):
for tot_bal in totals), for tot_bal in totals),
) )
def write_cash_flows(self) -> None:
self.use_sheet("Cash Flows")
bal_kwargs: Sequence[Dict[str, Any]] = [
{'period': Period.PERIOD},
{'period': Period.PRIOR},
]
col_count = len(bal_kwargs) + 1
for index in range(col_count):
col_style = self.column_style(1.5 if index else 3)
self.sheet.addElement(odf.table.TableColumn(stylename=col_style))
self.add_row(
self.multiline_cell([
"DRAFT Statement of Cash Flows",
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.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 self.EQUITY_ACCOUNTS), core.MutableBalance())
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:
balances = [
-self.balances.total(classification=classification, **kwargs)
for kwargs in bal_kwargs
]
self.add_row(
text_cell,
*(self.balance_cell(bal) for bal in balances),
)
for total, bal in zip(totals, balances):
total += bal
self.add_row(self.string_cell(
"Increase (decrease) in operating liabilities:",
))
for text, classification in self.walk_classifications_by_account('Liabilities'):
text_cell = self.string_cell(self.SPACE + text)
if classification is None:
self.add_row(text_cell)
else:
balances = [
-self.balances.total(classification=classification, **kwargs)
for kwargs in bal_kwargs
]
self.add_row(
text_cell,
*(self.balance_cell(bal) for bal in balances),
)
for total, bal in zip(totals, balances):
total += bal
self.add_row(
self.string_cell("Net cash provided by operating activites"),
*(self.balance_cell(tot_bal, stylename=self.style_totline)
for tot_bal in totals),
)
self.add_row()
self.add_row(
self.string_cell("Net Increase in Cash"),
*(self.balance_cell(tot_bal) for tot_bal in totals),
)
self.add_row()
balances = [
self.balances.total(classification=self.C_CASH, period=period)
for period in [Period.BEFORE_PERIOD, Period.OPENING]
]
self.add_row(
self.string_cell("Beginning Cash"),
*(self.balance_cell(bal) for bal in balances),
)
self.add_row()
self.add_row(
self.string_cell("Ending Cash"),
*(self.balance_cell(tot + bal, stylename=self.style_bottomline)
for tot, bal in zip(totals, balances)),
)
def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace: def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
parser = argparse.ArgumentParser(prog=PROGNAME) parser = argparse.ArgumentParser(prog=PROGNAME)

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup( setup(
name='conservancy_beancount', name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books", description="Plugin, library, and reports for reading Conservancy's books",
version='1.7.4', version='1.8.0',
author='Software Freedom Conservancy', author='Software Freedom Conservancy',
author_email='info@sfconservancy.org', author_email='info@sfconservancy.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',