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:
parent
f192d250e7
commit
948d3a2d14
4 changed files with 38 additions and 6 deletions
|
@ -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')),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -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+',
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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):
|
||||||
|
|
Loading…
Reference in a new issue