Changeset - 7441f4ef0ce1
[Not reviewed]
0 3 0
Brett Smith - 4 years ago 2020-06-17 22:25:47
brettcsmith@brettcsmith.org
ledger: Correct period totals. RT#11661.

The period totals were reporting the balance of all the loaded postings, not
just the ones in the reporting date range.

Like the accrual report, introduce a RelatedPostings subclass that records
and saves all the information we need at group definition time, to help us
get it consistently right rather than redoing the same math over and over.
3 files changed with 39 insertions and 17 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reports/ledger.py
Show inline comments
...
 
@@ -69,6 +69,7 @@ from pathlib import Path
 

	
 
import odf.table  # type:ignore[import]
 

	
 
from beancount.core import data as bc_data
 
from beancount.parser import printer as bc_printer
 

	
 
from . import core
...
 
@@ -84,6 +85,20 @@ PostTally = List[Tuple[int, data.Account]]
 
PROGNAME = 'ledger-report'
 
logger = logging.getLogger('conservancy_beancount.reports.ledger')
 

	
 
class AccountPostings(core.RelatedPostings):
 
    START_DATE: datetime.date
 

	
 
    def __init__(self,
 
                 source: Iterable[data.Posting]=(),
 
                 *,
 
                 _can_own: bool=False,
 
    ) -> None:
 
        super().__init__(source, _can_own=_can_own)
 
        self.start_bal = self.balance_at_cost_by_date(self.START_DATE)
 
        self.stop_bal = self.balance_at_cost()
 
        self.period_bal = self.stop_bal - self.start_bal
 

	
 

	
 
class LedgerODS(core.BaseODS[data.Posting, data.Account]):
 
    CORE_COLUMNS: Sequence[str] = [
 
        'Date',
...
 
@@ -268,17 +283,21 @@ class LedgerODS(core.BaseODS[data.Posting, data.Account]):
 

	
 
    def _report_section_balance(self, key: data.Account, date_key: str) -> None:
 
        uses_opening = key.is_under('Assets', 'Equity', 'Liabilities')
 
        related = self.account_groups[key]
 
        if date_key == 'start':
 
            if not uses_opening:
 
                return
 
            date = self.date_range.start
 
            balance = related.start_bal
 
            description = "Opening Balance"
 
        else:
 
            date = self.date_range.stop
 
            description = "Ending Balance" if uses_opening else "Period Total"
 
        balance = self.norm_func(
 
            self.account_groups[key].balance_at_cost_by_date(date)
 
        )
 
            if uses_opening:
 
                balance = related.stop_bal
 
                description = "Ending Balance"
 
            else:
 
                balance = related.period_bal
 
                description = "Period Total"
 
        self.add_row(
 
            self.date_cell(date, stylename=self.merge_styles(
 
                self.style_bold, self.style_date,
...
 
@@ -286,7 +305,7 @@ class LedgerODS(core.BaseODS[data.Posting, data.Account]):
 
            odf.table.TableCell(),
 
            self.string_cell(description, stylename=self.style_bold),
 
            odf.table.TableCell(),
 
            self.balance_cell(balance, stylename=self.style_bold),
 
            self.balance_cell(self.norm_func(balance), stylename=self.style_bold),
 
        )
 

	
 
    def start_section(self, key: data.Account) -> None:
...
 
@@ -327,11 +346,13 @@ class LedgerODS(core.BaseODS[data.Posting, data.Account]):
 
        )
 

	
 
    def _combined_balance_row(self,
 
                              date: datetime.date,
 
                              balance_accounts: Sequence[str],
 
                              attr_name: str,
 
    ) -> None:
 
        date = getattr(self.date_range, attr_name)
 
        balance_attrname = f'{attr_name}_bal'
 
        balance = -sum((
 
            related.balance_at_cost_by_date(date)
 
            getattr(related, balance_attrname)
 
            for account, related in self.account_groups.items()
 
            if account.is_under(*balance_accounts)
 
        ), core.MutableBalance())
...
 
@@ -365,23 +386,23 @@ class LedgerODS(core.BaseODS[data.Posting, data.Account]):
 
            numbercolumnsspanned=2,
 
        ))
 
        self.add_row()
 
        self._combined_balance_row(self.date_range.start, balance_accounts)
 
        self._combined_balance_row(balance_accounts, 'start')
 
        for _, account in self._sort_and_filter_accounts(
 
                self.account_groups, balance_accounts,
 
        ):
 
            related = self.account_groups[account]
 
            # start_bal - stop_bal == -(stop_bal - start_bal)
 
            balance = related.balance_at_cost_by_date(self.date_range.start)
 
            balance -= related.balance_at_cost_by_date(self.date_range.stop)
 
            balance = self.account_groups[account].period_bal
 
            if not balance.is_zero():
 
                self.add_row(
 
                    self.string_cell(account, stylename=self.style_endtext),
 
                    self.balance_cell(balance),
 
                    self.balance_cell(-balance),
 
                )
 
        self._combined_balance_row(self.date_range.stop, balance_accounts)
 
        self._combined_balance_row(balance_accounts, 'stop')
 

	
 
    def write(self, rows: Iterable[data.Posting]) -> None:
 
        self.account_groups = dict(core.RelatedPostings.group_by_account(rows))
 
        AccountPostings.START_DATE = self.date_range.start
 
        self.account_groups = dict(AccountPostings.group_by_account(
 
            post for post in rows if post.meta.date < self.date_range.stop
 
        ))
 
        self.write_balance_sheet()
 
        tally_by_account_iter = (
 
            (account, sum(1 for post in related if post.meta.date in self.date_range))
setup.py
Show inline comments
...
 
@@ -5,7 +5,7 @@ from setuptools import setup
 
setup(
 
    name='conservancy_beancount',
 
    description="Plugin, library, and reports for reading Conservancy's books",
 
    version='1.2.5',
 
    version='1.2.6',
 
    author='Software Freedom Conservancy',
 
    author_email='info@sfconservancy.org',
 
    license='GNU AGPLv3+',
tests/test_reports_ledger.py
Show inline comments
...
 
@@ -109,10 +109,12 @@ class ExpectedPostings(core.RelatedPostings):
 
                raise NoHeader(account)
 
            else:
 
                return
 
        closing_bal = norm_func(expect_posts.balance_at_cost())
 
        if account.is_under('Assets', 'Equity', 'Liabilities'):
 
            opening_row = testutil.ODSCell.from_row(next(rows))
 
            assert opening_row[0].value == start_date
 
            assert opening_row[4].text == open_bal.format(None, empty='0', sep='\0')
 
            closing_bal += open_bal
 
        for expected in expect_posts:
 
            cells = iter(testutil.ODSCell.from_row(next(rows)))
 
            assert next(cells).value == expected.meta.date
...
 
@@ -125,7 +127,6 @@ class ExpectedPostings(core.RelatedPostings):
 
                assert next(cells).value == norm_func(expected.units.number)
 
                assert next(cells).value == norm_func(expected.at_cost().number)
 
        closing_row = testutil.ODSCell.from_row(next(rows))
 
        closing_bal = open_bal + norm_func(expect_posts.balance_at_cost())
 
        assert closing_row[0].value == end_date
 
        assert closing_row[4].text == closing_bal.format(None, empty='0', sep='\0')
 

	
0 comments (0 inline, 0 general)