From 948d3a2d14d1376d124acb33d3f43c945035332c Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Tue, 9 Jun 2020 15:56:59 -0400 Subject: [PATCH] accrual: Add columns to the aging report. RT#11439. This adds almost all the metadata that's relevant to accruals. I considered adding statement, but that cuased rows to get spaced out a lot, and statement's kind of a low-value column, so I decided against it. Ultimately I would like to make this configurable but that's for the future. --- conservancy_beancount/reports/accrual.py | 12 +++++++++++- setup.py | 2 +- tests/books/accruals.beancount | 18 ++++++++++++++++++ tests/test_reports_accrual.py | 12 ++++++++---- 4 files changed, 38 insertions(+), 6 deletions(-) diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 54c12a2..6bf23f0 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -334,8 +334,12 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): 'Entity', 'Invoice Amount', 'Booked Amount', + 'Project', 'Ticket', 'Invoice', + 'Approval', + 'Contract', + 'Purchase Order', ] COL_COUNT = len(COLUMNS) @@ -402,7 +406,7 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): return total_balance = core.MutableBalance() text_style = self.merge_styles(self.style_bold, self.style_endtext) - text_span = self.COL_COUNT - 1 + text_span = 4 last_age_text: Optional[str] = None self.add_row() for threshold, balance in zip(self.age_thresholds, self.age_balances): @@ -474,13 +478,19 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]): amount_cell = odf.table.TableCell() else: amount_cell = self.balance_cell(raw_balance) + projects = {post.meta.get('project') or None for post in row} + projects.discard(None) self.add_row( self.date_cell(row[0].meta.date), self.multiline_cell(row.entities()), amount_cell, self.balance_cell(row.end_balance), + self.multiline_cell(sorted(projects)), self.multilink_cell(self._link_seq(row, 'rt-id')), self.multilink_cell(self._link_seq(row, 'invoice')), + self.multilink_cell(self._link_seq(row, 'approval')), + self.multilink_cell(self._link_seq(row, 'contract')), + self.multilink_cell(self._link_seq(row, 'purchase-order')), ) diff --git a/setup.py b/setup.py index 2ce3782..fcf10ce 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.1.5', + version='1.1.6', author='Software Freedom Conservancy', author_email='info@sfconservancy.org', license='GNU AGPLv3+', diff --git a/tests/books/accruals.beancount b/tests/books/accruals.beancount index e49d039..afa8169 100644 --- a/tests/books/accruals.beancount +++ b/tests/books/accruals.beancount @@ -15,36 +15,42 @@ 2010-03-05 * "EarlyBird" "Payment for receivable from previous FY" rt-id: "rt:40" invoice: "rt:40/400" + project: "Conservancy" Assets:Receivable:Accounts -500 USD Assets:Checking 500 USD 2010-03-06 * "EarlyBird" "Payment for payment from previous FY" rt-id: "rt:44" invoice: "rt:44/440" + project: "Conservancy" Liabilities:Payable:Accounts 125 USD Assets:Checking -125 USD 2010-03-15 * "GrantCo" "2010Q1 grant" rt-id: "rt:470" invoice: "rt:470/4700" + project: "Development Grant" Assets:Receivable:Accounts 5000 USD Income:Donations -5000 USD 2010-03-25 * "GrantCo" "2010Q1 grant ACH payment" rt-id: "rt:470" invoice: "rt:470/4700" + project: "Development Grant" Assets:Receivable:Accounts -5000 USD Assets:Checking 5000 USD 2010-03-30 * "EarlyBird" "Travel reimbursement" rt-id: "rt:490" invoice: "rt:490/4900" + project: "Conservancy" Liabilities:Payable:Accounts -75 USD Expenses:Travel 75 USD 2010-04-15 * "Multiparty invoice" rt-id: "rt:480" invoice: "rt:480/4800" + project: "Conservancy" Expenses:Travel 250 USD Liabilities:Payable:Accounts -125 USD entity: "MultiPartyA" @@ -54,12 +60,14 @@ 2010-04-18 * "MultiPartyA" "Payment for 480" rt-id: "rt:480" invoice: "rt:480/4800" + project: "Conservancy" Liabilities:Payable:Accounts 125 USD Assets:Checking -125 USD 2010-04-20 * "MultiPartyB" "Payment for 480" rt-id: "rt:480" invoice: "rt:480/4800" + project: "Conservancy" Liabilities:Payable:Accounts 125 USD Assets:Checking -125 USD @@ -67,6 +75,7 @@ rt-id: "rt:310" contract: "rt:310/3100" invoice: "FIXME" ; still waiting on them to send it + project: "Conservancy" Liabilities:Payable:Accounts -200 USD Expenses:Travel 200 USD @@ -74,6 +83,7 @@ rt-id: "rt:505" invoice: "rt:505/5050" approval: "rt:505/5040" + project: "Conservancy" Income:Donations -2,500 EUR {1.100 USD} Assets:Receivable:Accounts 2,500 EUR {1.100 USD} @@ -81,18 +91,21 @@ rt-id: "rt:510" invoice: "rt:510/5100" contract: "rt:510/4000" + project: "Conservancy" Expenses:Services:Legal 200.00 USD Liabilities:Payable:Accounts -200.00 USD 2010-05-15 * "MatchingProgram" "May matched donations" invoice: "rt://ticket/515/attachments/5150" approval: "rt://ticket/515/attachments/5140" + project: "Conservancy" Income:Donations -1500.00 USD Assets:Receivable:Accounts 1500.00 USD 2010-05-20 * "DonorA" "Donation made" rt-id: "rt:505" invoice: "rt:505/5050" + project: "Conservancy" Assets:Receivable:Accounts -2,750.00 USD Assets:Checking 2,750.00 USD receipt: "DonorAWire.pdf" @@ -100,6 +113,7 @@ 2010-05-25 * "Lawyer" "May payment" rt-id: "rt:510" invoice: "rt:510/5100" + project: "Conservancy" Liabilities:Payable:Accounts 200.00 USD contract: "rt:510/4000" Assets:Checking -200.00 USD @@ -109,6 +123,7 @@ rt-id: "rt:510" invoice: "rt:510/6100" contract: "rt:510/4000" + project: "Conservancy" Expenses:Services:Legal 220.00 USD Liabilities:Payable:Accounts -220.00 USD @@ -116,6 +131,7 @@ rt-id: "rt:510" invoice: "rt:510/6100" contract: "rt:510/4000" + project: "Conservancy" Expenses:FilingFees 60.00 USD Liabilities:Payable:Accounts -60.00 USD @@ -125,11 +141,13 @@ rt-id: "rt:520 rt:525" invoice: "rt:520/5200" contract: "rt:520/5220" + project: "Conservancy" Liabilities:Payable:Accounts -1,000 EUR {1.100 USD} Expenses:FilingFees 1,000 EUR {1.100 USD} 2010-06-15 * "GrantCo" "2010Q2 grant" rt-id: "rt:470" invoice: "rt:470/4700" + project: "Development Grant" Assets:Receivable:Accounts 5500 USD Income:Donations -5500 USD diff --git a/tests/test_reports_accrual.py b/tests/test_reports_accrual.py index d6f894d..8b90dbb 100644 --- a/tests/test_reports_accrual.py +++ b/tests/test_reports_accrual.py @@ -75,20 +75,22 @@ class AgingRow(NamedTuple): at_cost: bc_data.Amount rt_id: Sequence[str] invoice: Sequence[str] + project: Sequence[str] @classmethod - def make_simple(cls, date, entity, at_cost, invoice, rt_id=None, orig_amount=None): + def make_simple(cls, date, entity, at_cost, invoice, + rt_id=None, orig_amount=None, project='Conservancy'): if isinstance(date, str): date = datetime.datetime.strptime(date, '%Y-%m-%d').date() if not isinstance(at_cost, tuple): at_cost = testutil.Amount(at_cost) if rt_id is None: rt_id, _, _ = invoice.partition('/') - return cls(date, [entity], orig_amount, at_cost, [rt_id], [invoice]) + return cls(date, [entity], orig_amount, at_cost, [rt_id], [invoice], [project]) def check_row_match(self, sheet_row): cells = testutil.ODSCell.from_row(sheet_row) - assert len(cells) == len(self) + assert len(cells) >= len(self) cells = iter(cells) assert next(cells).value == self.date assert next(cells).text == '\0'.join(self.entity) @@ -99,6 +101,7 @@ class AgingRow(NamedTuple): usd_cell = next(cells) assert usd_cell.value_type == 'currency' assert usd_cell.value == self.at_cost.number + assert next(cells).text == '\0'.join(self.project) for index, cell in enumerate(cells): links = cell.getElementsByType(odf.text.A) assert len(links) == len(cell.childNodes) @@ -118,7 +121,8 @@ AGING_AR = [ AgingRow.make_simple('2010-03-05', 'EarlyBird', -500, 'rt:40/400'), AgingRow.make_simple('2010-05-15', 'MatchingProgram', 1500, 'rt://ticket/515/attachments/5150'), - AgingRow.make_simple('2010-06-15', 'GrantCo', 5500, 'rt:470/4700'), + AgingRow.make_simple('2010-06-15', 'GrantCo', 5500, 'rt:470/4700', + project='Development Grant'), ] class RTClient(testutil.RTClient):