diff --git a/conservancy_beancount/reports/balance_sheet.py b/conservancy_beancount/reports/balance_sheet.py index 1f6f93acff47eb667f3f6b19e255aaa2bfe9249a..ef96940b14705165c71f104bb255290602e6a273 100644 --- a/conservancy_beancount/reports/balance_sheet.py +++ b/conservancy_beancount/reports/balance_sheet.py @@ -189,6 +189,15 @@ class Balances: return prefix, -max_bal return sorted(class_bals, key=sortkey) + def iter_accounts(self, root: str) -> Iterable[data.Account]: + start_date = self.period_range.start + stop_date = self.period_range.stop + for account in data.Account.iter_accounts(root): + meta = account.meta + if (meta.open_date < stop_date + and (meta.close_date is None or meta.close_date > start_date)): + yield account + class Report(core.BaseODS[Sequence[None], None]): C_CASH = 'Cash' @@ -233,6 +242,7 @@ class Report(core.BaseODS[Sequence[None], None]): self.write_activities() self.write_functional_expenses() self.write_cash_flows() + self.write_chart_of_accounts() def walk_classifications(self, cseq: Iterable[data.Account]) \ -> Iterator[Tuple[str, Optional[data.Account]]]: @@ -261,6 +271,7 @@ class Report(core.BaseODS[Sequence[None], None]): totals_prefix: Sequence[str]=(), first_width: Union[float, str]=3, width: Union[float, str]=1.5, + title_fmt: str="DRAFT Statement of {sheet_name}", ) -> None: header_cells: Sequence[odf.table.TableCell] = [ odf.table.TableCell(), @@ -277,7 +288,7 @@ class Report(core.BaseODS[Sequence[None], None]): start_date = self.balances.period_range.start.strftime(self.date_fmt) self.add_row( self.multiline_cell([ - f"DRAFT Statement of {sheet_name}", + title_fmt.format(sheet_name=sheet_name), f"{start_date}—{self.period_name}", ], numbercolumnsspanned=self.col_count, stylename=self.style_header) ) @@ -554,6 +565,45 @@ class Report(core.BaseODS[Sequence[None], None]): stylename=self.style_bottomline, ) + def write_chart_of_accounts(self) -> None: + self.start_sheet( + "Chart of Accounts", + ["Account Name"], ["Classification"], + totals_prefix=["Change During", "Year Ending"], + title_fmt="{sheet_name}", + ) + # Widen text columns + col_style = self.column_style(3.5) + for col in self.sheet.childNodes[:2]: + col.setAttribute('stylename', col_style) + # Patch up header row text + header_row = self.sheet.lastChild + header_row.removeChild(header_row.firstChild) + header_row.addElement(self.multiline_cell( + ["Balance Ending", self.period_name], + stylename=header_row.lastChild.getAttribute('stylename'), + )) + + for acct_root in ['Assets', 'Liabilities', 'Income', 'Expenses', 'Equity']: + norm_func = core.normalize_amount_func(f'{acct_root}:Dummy') + want_balance = acct_root not in EQUITY_ACCOUNTS + self.add_row() + for account in self.balances.iter_accounts(acct_root): + period_bal = self.balances.total(account=account, period=Period.PERIOD) + prior_bal = self.balances.total(account=account, period=Period.PRIOR) + if want_balance: + total_bal = self.balances.total(account=account) + total_cell = self.balance_cell(norm_func(total_bal)) + else: + total_cell = odf.table.TableCell() + self.add_row( + self.string_cell(account), + self.string_cell(account.meta.get('classification', '')), + self.balance_cell(norm_func(period_bal)), + self.balance_cell(norm_func(prior_bal)), + total_cell, + ) + def parse_arguments(arglist: Optional[Sequence[str]]=None) -> argparse.Namespace: parser = argparse.ArgumentParser(prog=PROGNAME) diff --git a/setup.py b/setup.py index 867d9d5125c71e05a1c13d07d0c96026556ef554..8f4a501c385e158c2fddced0cbb3f38530ab61b6 100755 --- a/setup.py +++ b/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.8.0', + version='1.8.1', author='Software Freedom Conservancy', author_email='info@sfconservancy.org', license='GNU AGPLv3+',