balance_sheet: Add functional expenses report.
This commit is contained in:
parent
29d4325c7a
commit
eaaf8fe98c
2 changed files with 69 additions and 3 deletions
|
@ -218,6 +218,7 @@ class Report(core.BaseODS[Sequence[None], None]):
|
|||
def write_all(self) -> None:
|
||||
self.write_financial_position()
|
||||
self.write_activities()
|
||||
self.write_functional_expenses()
|
||||
|
||||
def walk_classifications(self, cseq: Iterable[data.Account]) \
|
||||
-> Iterator[Tuple[str, Optional[data.Account]]]:
|
||||
|
@ -225,9 +226,9 @@ class Report(core.BaseODS[Sequence[None], None]):
|
|||
for classification in cseq:
|
||||
parts = classification.split(':')
|
||||
tail = parts.pop()
|
||||
tabs = '\t' * len(parts)
|
||||
tabs = ' ' * 4 * len(parts)
|
||||
if parts != last_prefix:
|
||||
yield f'{tabs[1:]}{parts[-1]}', None
|
||||
yield f'{tabs[4:]}{parts[-1]}', None
|
||||
last_prefix = parts
|
||||
yield f'{tabs}{tail}', classification
|
||||
|
||||
|
@ -509,6 +510,71 @@ class Report(core.BaseODS[Sequence[None], None]):
|
|||
for beg_bal, tot_bal in zip(beginnings, totals)),
|
||||
)
|
||||
|
||||
def write_functional_expenses(self) -> None:
|
||||
self.use_sheet("Functional Expenses")
|
||||
bal_kwargs: Sequence[Dict[str, Any]] = [
|
||||
{'period': Period.PERIOD, 'post_type': 'program'},
|
||||
{'period': Period.PERIOD, 'post_type': 'management'},
|
||||
{'period': Period.PERIOD, 'post_type': 'fundraising'},
|
||||
{'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 Functional Expenses",
|
||||
self.period_name,
|
||||
], numbercolumnsspanned=col_count, stylename=self.style_header)
|
||||
)
|
||||
self.add_row()
|
||||
self.add_row(
|
||||
odf.table.TableCell(),
|
||||
self.multiline_cell(["Program", "Services"],
|
||||
stylename=self.style_huline),
|
||||
self.multiline_cell(["Management and", "Administrative"],
|
||||
stylename=self.style_huline),
|
||||
self.multiline_cell(["Fundraising"],
|
||||
stylename=self.style_huline),
|
||||
self.multiline_cell(["Total Year Ended", self.period_name],
|
||||
stylename=self.style_huline),
|
||||
self.multiline_cell(["Total Year Ended", self.opening_name],
|
||||
stylename=self.style_huline),
|
||||
)
|
||||
self.add_row()
|
||||
|
||||
totals = [core.MutableBalance() for _ in bal_kwargs]
|
||||
for text, classification in self.walk_classifications_by_account('Expenses'):
|
||||
text_cell = self.string_cell(text)
|
||||
if classification is None:
|
||||
if not text[0].isspace():
|
||||
self.add_row()
|
||||
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),
|
||||
)
|
||||
break_bal = sum(balances[:3], core.MutableBalance())
|
||||
if not (break_bal - balances[3]).clean_copy(1).is_zero():
|
||||
logger.warning(
|
||||
"Functional expenses breakdown does not match total on row %s",
|
||||
len(self.sheet.childNodes) - col_count,
|
||||
)
|
||||
for total, bal in zip(totals, balances):
|
||||
total += bal
|
||||
self.add_row()
|
||||
self.add_row(
|
||||
self.string_cell("Total Expenses"),
|
||||
*(self.balance_cell(tot_bal, stylename=self.style_bottomline)
|
||||
for tot_bal in totals),
|
||||
)
|
||||
|
||||
|
||||
def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace:
|
||||
|
|
2
setup.py
2
setup.py
|
@ -5,7 +5,7 @@ from setuptools import setup
|
|||
setup(
|
||||
name='conservancy_beancount',
|
||||
description="Plugin, library, and reports for reading Conservancy's books",
|
||||
version='1.7.3',
|
||||
version='1.7.4',
|
||||
author='Software Freedom Conservancy',
|
||||
author_email='info@sfconservancy.org',
|
||||
license='GNU AGPLv3+',
|
||||
|
|
Loading…
Reference in a new issue