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.
This commit is contained in:
Brett Smith 2020-06-09 15:56:59 -04:00
parent f192d250e7
commit 948d3a2d14
4 changed files with 38 additions and 6 deletions

View file

@ -334,8 +334,12 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
'Entity', 'Entity',
'Invoice Amount', 'Invoice Amount',
'Booked Amount', 'Booked Amount',
'Project',
'Ticket', 'Ticket',
'Invoice', 'Invoice',
'Approval',
'Contract',
'Purchase Order',
] ]
COL_COUNT = len(COLUMNS) COL_COUNT = len(COLUMNS)
@ -402,7 +406,7 @@ class AgingODS(core.BaseODS[AccrualPostings, Optional[data.Account]]):
return return
total_balance = core.MutableBalance() total_balance = core.MutableBalance()
text_style = self.merge_styles(self.style_bold, self.style_endtext) 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 last_age_text: Optional[str] = None
self.add_row() self.add_row()
for threshold, balance in zip(self.age_thresholds, self.age_balances): 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() amount_cell = odf.table.TableCell()
else: else:
amount_cell = self.balance_cell(raw_balance) 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.add_row(
self.date_cell(row[0].meta.date), self.date_cell(row[0].meta.date),
self.multiline_cell(row.entities()), self.multiline_cell(row.entities()),
amount_cell, amount_cell,
self.balance_cell(row.end_balance), 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, 'rt-id')),
self.multilink_cell(self._link_seq(row, 'invoice')), 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')),
) )

View file

@ -5,7 +5,7 @@ from setuptools import setup
setup( setup(
name='conservancy_beancount', name='conservancy_beancount',
description="Plugin, library, and reports for reading Conservancy's books", description="Plugin, library, and reports for reading Conservancy's books",
version='1.1.5', version='1.1.6',
author='Software Freedom Conservancy', author='Software Freedom Conservancy',
author_email='info@sfconservancy.org', author_email='info@sfconservancy.org',
license='GNU AGPLv3+', license='GNU AGPLv3+',

View file

@ -15,36 +15,42 @@
2010-03-05 * "EarlyBird" "Payment for receivable from previous FY" 2010-03-05 * "EarlyBird" "Payment for receivable from previous FY"
rt-id: "rt:40" rt-id: "rt:40"
invoice: "rt:40/400" invoice: "rt:40/400"
project: "Conservancy"
Assets:Receivable:Accounts -500 USD Assets:Receivable:Accounts -500 USD
Assets:Checking 500 USD Assets:Checking 500 USD
2010-03-06 * "EarlyBird" "Payment for payment from previous FY" 2010-03-06 * "EarlyBird" "Payment for payment from previous FY"
rt-id: "rt:44" rt-id: "rt:44"
invoice: "rt:44/440" invoice: "rt:44/440"
project: "Conservancy"
Liabilities:Payable:Accounts 125 USD Liabilities:Payable:Accounts 125 USD
Assets:Checking -125 USD Assets:Checking -125 USD
2010-03-15 * "GrantCo" "2010Q1 grant" 2010-03-15 * "GrantCo" "2010Q1 grant"
rt-id: "rt:470" rt-id: "rt:470"
invoice: "rt:470/4700" invoice: "rt:470/4700"
project: "Development Grant"
Assets:Receivable:Accounts 5000 USD Assets:Receivable:Accounts 5000 USD
Income:Donations -5000 USD Income:Donations -5000 USD
2010-03-25 * "GrantCo" "2010Q1 grant ACH payment" 2010-03-25 * "GrantCo" "2010Q1 grant ACH payment"
rt-id: "rt:470" rt-id: "rt:470"
invoice: "rt:470/4700" invoice: "rt:470/4700"
project: "Development Grant"
Assets:Receivable:Accounts -5000 USD Assets:Receivable:Accounts -5000 USD
Assets:Checking 5000 USD Assets:Checking 5000 USD
2010-03-30 * "EarlyBird" "Travel reimbursement" 2010-03-30 * "EarlyBird" "Travel reimbursement"
rt-id: "rt:490" rt-id: "rt:490"
invoice: "rt:490/4900" invoice: "rt:490/4900"
project: "Conservancy"
Liabilities:Payable:Accounts -75 USD Liabilities:Payable:Accounts -75 USD
Expenses:Travel 75 USD Expenses:Travel 75 USD
2010-04-15 * "Multiparty invoice" 2010-04-15 * "Multiparty invoice"
rt-id: "rt:480" rt-id: "rt:480"
invoice: "rt:480/4800" invoice: "rt:480/4800"
project: "Conservancy"
Expenses:Travel 250 USD Expenses:Travel 250 USD
Liabilities:Payable:Accounts -125 USD Liabilities:Payable:Accounts -125 USD
entity: "MultiPartyA" entity: "MultiPartyA"
@ -54,12 +60,14 @@
2010-04-18 * "MultiPartyA" "Payment for 480" 2010-04-18 * "MultiPartyA" "Payment for 480"
rt-id: "rt:480" rt-id: "rt:480"
invoice: "rt:480/4800" invoice: "rt:480/4800"
project: "Conservancy"
Liabilities:Payable:Accounts 125 USD Liabilities:Payable:Accounts 125 USD
Assets:Checking -125 USD Assets:Checking -125 USD
2010-04-20 * "MultiPartyB" "Payment for 480" 2010-04-20 * "MultiPartyB" "Payment for 480"
rt-id: "rt:480" rt-id: "rt:480"
invoice: "rt:480/4800" invoice: "rt:480/4800"
project: "Conservancy"
Liabilities:Payable:Accounts 125 USD Liabilities:Payable:Accounts 125 USD
Assets:Checking -125 USD Assets:Checking -125 USD
@ -67,6 +75,7 @@
rt-id: "rt:310" rt-id: "rt:310"
contract: "rt:310/3100" contract: "rt:310/3100"
invoice: "FIXME" ; still waiting on them to send it invoice: "FIXME" ; still waiting on them to send it
project: "Conservancy"
Liabilities:Payable:Accounts -200 USD Liabilities:Payable:Accounts -200 USD
Expenses:Travel 200 USD Expenses:Travel 200 USD
@ -74,6 +83,7 @@
rt-id: "rt:505" rt-id: "rt:505"
invoice: "rt:505/5050" invoice: "rt:505/5050"
approval: "rt:505/5040" approval: "rt:505/5040"
project: "Conservancy"
Income:Donations -2,500 EUR {1.100 USD} Income:Donations -2,500 EUR {1.100 USD}
Assets:Receivable:Accounts 2,500 EUR {1.100 USD} Assets:Receivable:Accounts 2,500 EUR {1.100 USD}
@ -81,18 +91,21 @@
rt-id: "rt:510" rt-id: "rt:510"
invoice: "rt:510/5100" invoice: "rt:510/5100"
contract: "rt:510/4000" contract: "rt:510/4000"
project: "Conservancy"
Expenses:Services:Legal 200.00 USD Expenses:Services:Legal 200.00 USD
Liabilities:Payable:Accounts -200.00 USD Liabilities:Payable:Accounts -200.00 USD
2010-05-15 * "MatchingProgram" "May matched donations" 2010-05-15 * "MatchingProgram" "May matched donations"
invoice: "rt://ticket/515/attachments/5150" invoice: "rt://ticket/515/attachments/5150"
approval: "rt://ticket/515/attachments/5140" approval: "rt://ticket/515/attachments/5140"
project: "Conservancy"
Income:Donations -1500.00 USD Income:Donations -1500.00 USD
Assets:Receivable:Accounts 1500.00 USD Assets:Receivable:Accounts 1500.00 USD
2010-05-20 * "DonorA" "Donation made" 2010-05-20 * "DonorA" "Donation made"
rt-id: "rt:505" rt-id: "rt:505"
invoice: "rt:505/5050" invoice: "rt:505/5050"
project: "Conservancy"
Assets:Receivable:Accounts -2,750.00 USD Assets:Receivable:Accounts -2,750.00 USD
Assets:Checking 2,750.00 USD Assets:Checking 2,750.00 USD
receipt: "DonorAWire.pdf" receipt: "DonorAWire.pdf"
@ -100,6 +113,7 @@
2010-05-25 * "Lawyer" "May payment" 2010-05-25 * "Lawyer" "May payment"
rt-id: "rt:510" rt-id: "rt:510"
invoice: "rt:510/5100" invoice: "rt:510/5100"
project: "Conservancy"
Liabilities:Payable:Accounts 200.00 USD Liabilities:Payable:Accounts 200.00 USD
contract: "rt:510/4000" contract: "rt:510/4000"
Assets:Checking -200.00 USD Assets:Checking -200.00 USD
@ -109,6 +123,7 @@
rt-id: "rt:510" rt-id: "rt:510"
invoice: "rt:510/6100" invoice: "rt:510/6100"
contract: "rt:510/4000" contract: "rt:510/4000"
project: "Conservancy"
Expenses:Services:Legal 220.00 USD Expenses:Services:Legal 220.00 USD
Liabilities:Payable:Accounts -220.00 USD Liabilities:Payable:Accounts -220.00 USD
@ -116,6 +131,7 @@
rt-id: "rt:510" rt-id: "rt:510"
invoice: "rt:510/6100" invoice: "rt:510/6100"
contract: "rt:510/4000" contract: "rt:510/4000"
project: "Conservancy"
Expenses:FilingFees 60.00 USD Expenses:FilingFees 60.00 USD
Liabilities:Payable:Accounts -60.00 USD Liabilities:Payable:Accounts -60.00 USD
@ -125,11 +141,13 @@
rt-id: "rt:520 rt:525" rt-id: "rt:520 rt:525"
invoice: "rt:520/5200" invoice: "rt:520/5200"
contract: "rt:520/5220" contract: "rt:520/5220"
project: "Conservancy"
Liabilities:Payable:Accounts -1,000 EUR {1.100 USD} Liabilities:Payable:Accounts -1,000 EUR {1.100 USD}
Expenses:FilingFees 1,000 EUR {1.100 USD} Expenses:FilingFees 1,000 EUR {1.100 USD}
2010-06-15 * "GrantCo" "2010Q2 grant" 2010-06-15 * "GrantCo" "2010Q2 grant"
rt-id: "rt:470" rt-id: "rt:470"
invoice: "rt:470/4700" invoice: "rt:470/4700"
project: "Development Grant"
Assets:Receivable:Accounts 5500 USD Assets:Receivable:Accounts 5500 USD
Income:Donations -5500 USD Income:Donations -5500 USD

View file

@ -75,20 +75,22 @@ class AgingRow(NamedTuple):
at_cost: bc_data.Amount at_cost: bc_data.Amount
rt_id: Sequence[str] rt_id: Sequence[str]
invoice: Sequence[str] invoice: Sequence[str]
project: Sequence[str]
@classmethod @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): if isinstance(date, str):
date = datetime.datetime.strptime(date, '%Y-%m-%d').date() date = datetime.datetime.strptime(date, '%Y-%m-%d').date()
if not isinstance(at_cost, tuple): if not isinstance(at_cost, tuple):
at_cost = testutil.Amount(at_cost) at_cost = testutil.Amount(at_cost)
if rt_id is None: if rt_id is None:
rt_id, _, _ = invoice.partition('/') 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): def check_row_match(self, sheet_row):
cells = testutil.ODSCell.from_row(sheet_row) cells = testutil.ODSCell.from_row(sheet_row)
assert len(cells) == len(self) assert len(cells) >= len(self)
cells = iter(cells) cells = iter(cells)
assert next(cells).value == self.date assert next(cells).value == self.date
assert next(cells).text == '\0'.join(self.entity) assert next(cells).text == '\0'.join(self.entity)
@ -99,6 +101,7 @@ class AgingRow(NamedTuple):
usd_cell = next(cells) usd_cell = next(cells)
assert usd_cell.value_type == 'currency' assert usd_cell.value_type == 'currency'
assert usd_cell.value == self.at_cost.number assert usd_cell.value == self.at_cost.number
assert next(cells).text == '\0'.join(self.project)
for index, cell in enumerate(cells): for index, cell in enumerate(cells):
links = cell.getElementsByType(odf.text.A) links = cell.getElementsByType(odf.text.A)
assert len(links) == len(cell.childNodes) 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-03-05', 'EarlyBird', -500, 'rt:40/400'),
AgingRow.make_simple('2010-05-15', 'MatchingProgram', 1500, AgingRow.make_simple('2010-05-15', 'MatchingProgram', 1500,
'rt://ticket/515/attachments/5150'), '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): class RTClient(testutil.RTClient):