fund: Add bottom line totals to Fund Report. RT#4582.
This required keeping the balances from write_row, and then a lot of other changes followed from that. In particular it makes more sense to build the fund report sheet from scratch rather than copying the breakdowns report and chiseling the fund report out of it.
This commit is contained in:
parent
7a0fa4fb57
commit
7702a1f03c
2 changed files with 80 additions and 67 deletions
|
@ -61,6 +61,7 @@ from typing import (
|
|||
Sequence,
|
||||
TextIO,
|
||||
Tuple,
|
||||
Union,
|
||||
)
|
||||
from ..beancount_types import (
|
||||
MetaValue,
|
||||
|
@ -98,32 +99,48 @@ class ODSReport(core.BaseODS[FundPosts, None]):
|
|||
super().__init__()
|
||||
self.start_date = start_date
|
||||
self.stop_date = stop_date
|
||||
self.unrestricted: AccountsMap = {}
|
||||
|
||||
def section_key(self, row: FundPosts) -> None:
|
||||
return None
|
||||
|
||||
def start_spreadsheet(self) -> None:
|
||||
self.use_sheet("With Breakdowns")
|
||||
for width in [2.5, 1.5, 1.2, 1.2, 1.2, 1.5, 1.2, 1.3, 1.2, 1.3]:
|
||||
def start_spreadsheet(self, *, expanded: bool=True) -> None:
|
||||
headers = [["Fund"], ["Balance as of", self.start_date.isoformat()]]
|
||||
if expanded:
|
||||
sheet_name = "With Breakdowns"
|
||||
headers += [["Income"], ["Expenses"], ["Equity"]]
|
||||
else:
|
||||
sheet_name = "Fund Report"
|
||||
headers += [["Additions"], ["Releases from", "Restrictions"]]
|
||||
headers.append(["Balance as of", self.stop_date.isoformat()])
|
||||
if expanded:
|
||||
headers += [
|
||||
["Of which", "Receivable"],
|
||||
["Of which", "Prepaid Expenses"],
|
||||
["Of which", "Payable"],
|
||||
["Of which", "Unearned Income"],
|
||||
]
|
||||
|
||||
self.use_sheet(sheet_name)
|
||||
for header in headers:
|
||||
first_line = header[0]
|
||||
if first_line == 'Fund':
|
||||
width = 2.0
|
||||
elif first_line == 'Balance as of':
|
||||
width = 1.5
|
||||
elif first_line == 'Of which':
|
||||
width = 1.3
|
||||
else:
|
||||
width = 1.2
|
||||
col_style = self.column_style(width)
|
||||
self.sheet.addElement(odf.table.TableColumn(stylename=col_style))
|
||||
|
||||
center_bold = self.merge_styles(self.style_centertext, self.style_bold)
|
||||
self.add_row(
|
||||
self.string_cell(
|
||||
"Fund", stylename=self.merge_styles(self.style_endtext, self.style_bold),
|
||||
),
|
||||
self.multiline_cell(["Balance as of", self.start_date.isoformat()],
|
||||
stylename=center_bold),
|
||||
self.string_cell("Income", stylename=center_bold),
|
||||
self.string_cell("Expenses", stylename=center_bold),
|
||||
self.string_cell("Equity", stylename=center_bold),
|
||||
self.multiline_cell(["Balance as of", self.stop_date.isoformat()],
|
||||
stylename=center_bold),
|
||||
self.multiline_cell(["Of Which", "Receivable"], stylename=center_bold),
|
||||
self.multiline_cell(["Of Which", "Prepaid Expenses"], stylename=center_bold),
|
||||
self.multiline_cell(["Of Which", "Payable"], stylename=center_bold),
|
||||
self.multiline_cell(["Of Which", "Unearned Income"], stylename=center_bold),
|
||||
row = self.add_row(*(
|
||||
self.multiline_cell(header, stylename=center_bold)
|
||||
for header in headers
|
||||
))
|
||||
row.firstChild.setAttribute(
|
||||
'stylename', self.merge_styles(self.style_endtext, self.style_bold),
|
||||
)
|
||||
self.lock_first_row()
|
||||
self.lock_first_column()
|
||||
|
@ -136,45 +153,25 @@ class ODSReport(core.BaseODS[FundPosts, None]):
|
|||
self.add_row()
|
||||
|
||||
def end_spreadsheet(self) -> None:
|
||||
sheet = self.copy_element(self.sheet)
|
||||
sheet.setAttribute('name', 'Fund Report')
|
||||
row_qname = odf.table.TableRow().qname
|
||||
skip_rows: List[int] = []
|
||||
report_threshold = Decimal('.5')
|
||||
first_row = True
|
||||
for index, row in enumerate(sheet.childNodes):
|
||||
if len(row.childNodes) < 6:
|
||||
continue
|
||||
row.childNodes = [*row.childNodes[:4], row.childNodes[5]]
|
||||
if row.qname != row_qname:
|
||||
pass
|
||||
elif first_row:
|
||||
ref_child = row.childNodes[2]
|
||||
stylename = ref_child.getAttribute('stylename')
|
||||
row.insertBefore(self.string_cell(
|
||||
"Additions", stylename=stylename,
|
||||
), ref_child)
|
||||
row.insertBefore(self.multiline_cell(
|
||||
["Releases from", "Restrictions"], stylename=stylename,
|
||||
), ref_child)
|
||||
del row.childNodes[4:6]
|
||||
first_row = False
|
||||
# Filter out fund rows that don't have anything reportable.
|
||||
elif not any(
|
||||
# Multiple childNodes means it's a multi-currency balance.
|
||||
len(cell.childNodes) > 1
|
||||
# Some column has to round up to 1 to be reportable.
|
||||
or (cell.getAttribute('valuetype') == 'currency'
|
||||
and Decimal(cell.getAttribute('value')) >= report_threshold)
|
||||
for cell in row.childNodes
|
||||
):
|
||||
skip_rows.append(index)
|
||||
for index in reversed(skip_rows):
|
||||
del sheet.childNodes[index]
|
||||
self.lock_first_row(sheet)
|
||||
self.lock_first_column(sheet)
|
||||
self.document.spreadsheet.insertBefore(sheet, self.sheet)
|
||||
start_sheet = self.sheet
|
||||
self.set_open_sheet(self.sheet)
|
||||
self.start_spreadsheet(expanded=False)
|
||||
bal_indexes = [0, 1, 2, 4]
|
||||
totals = [core.MutableBalance() for _ in bal_indexes]
|
||||
threshold = Decimal('.5')
|
||||
for fund, balances in self.balances.items():
|
||||
balances = [balances[index] for index in bal_indexes]
|
||||
if (not all(bal.clean_copy(threshold).le_zero() for bal in balances)
|
||||
and fund != UNRESTRICTED_FUND):
|
||||
self.write_balances(fund, balances)
|
||||
for total, bal in zip(totals, balances):
|
||||
total += bal
|
||||
self.write_balances('', totals, self.merge_styles(
|
||||
self.border_style(core.Border.TOP, '.75pt'),
|
||||
self.border_style(core.Border.BOTTOM, '1.5pt', 'double'),
|
||||
))
|
||||
self.document.spreadsheet.childNodes.reverse()
|
||||
self.sheet = start_sheet
|
||||
|
||||
def _row_balances(self, accounts_map: AccountsMap) -> Iterable[core.Balance]:
|
||||
acct_order = ['Income', 'Expenses', 'Equity']
|
||||
|
@ -196,22 +193,32 @@ class ODSReport(core.BaseODS[FundPosts, None]):
|
|||
pass
|
||||
yield core.normalize_amount_func(info_key)(balance)
|
||||
|
||||
def write_row(self, row: FundPosts) -> None:
|
||||
fund, accounts_map = row
|
||||
if fund == UNRESTRICTED_FUND:
|
||||
assert not self.unrestricted
|
||||
self.unrestricted = accounts_map
|
||||
return
|
||||
self.add_row(
|
||||
def write_balances(self,
|
||||
fund: str,
|
||||
balances: Iterable[core.Balance],
|
||||
style: Union[None, str, odf.style.Style]=None,
|
||||
) -> odf.table.TableRow:
|
||||
return self.add_row(
|
||||
self.string_cell(fund, stylename=self.style_endtext),
|
||||
*(self.balance_cell(bal) for bal in self._row_balances(accounts_map)),
|
||||
*(self.balance_cell(bal, stylename=style) for bal in balances),
|
||||
)
|
||||
|
||||
def write_row(self, row: FundPosts) -> None:
|
||||
fund, accounts_map = row
|
||||
self.balances[fund] = list(self._row_balances(accounts_map))
|
||||
if fund != UNRESTRICTED_FUND:
|
||||
self.write_balances(fund, self.balances[fund])
|
||||
|
||||
def write(self, rows: Iterable[FundPosts]) -> None:
|
||||
self.balances: Dict[str, Sequence[core.Balance]] = collections.OrderedDict()
|
||||
super().write(rows)
|
||||
if self.unrestricted:
|
||||
try:
|
||||
unrestricted = self.balances[UNRESTRICTED_FUND]
|
||||
except KeyError:
|
||||
pass
|
||||
else:
|
||||
self.add_row()
|
||||
self.write_row(("Unrestricted", self.unrestricted))
|
||||
self.write_balances("Unrestricted", unrestricted)
|
||||
|
||||
|
||||
class TextReport:
|
||||
|
|
|
@ -172,6 +172,12 @@ def check_ods_sheet(sheet, account_balances, *, full):
|
|||
for key, balances in account_balances.items()
|
||||
if key != 'Conservancy' and any(v >= .5 for v in balances.values())
|
||||
}
|
||||
totals = {key: Decimal() for key in
|
||||
['opening', 'Income', 'Expenses', 'Equity:Realized']}
|
||||
for fund, balances in account_bals.items():
|
||||
for key in totals:
|
||||
totals[key] += balances[key]
|
||||
account_bals[''] = totals
|
||||
for row in itertools.islice(sheet.getElementsByType(odf.table.TableRow), 4, None):
|
||||
cells = iter(testutil.ODSCell.from_row(row))
|
||||
try:
|
||||
|
|
Loading…
Reference in a new issue