From c88c5ef3b05d6004cf530170cc0f70064bf5fe70 Mon Sep 17 00:00:00 2001 From: Brett Smith Date: Wed, 3 Jun 2020 21:23:52 -0400 Subject: [PATCH] accruals: AccrualPostings only reports inconsistent cost per currency. Of course if an accrual has multiple currencies, it'll probably have different costs, and that's fine. --- conservancy_beancount/reports/accrual.py | 14 +++++++++--- tests/books/accruals.beancount | 8 +++---- tests/test_reports_accrual.py | 28 ------------------------ 3 files changed, 15 insertions(+), 35 deletions(-) diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 86deed5..2bb819c 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -144,7 +144,6 @@ class AccrualPostings(core.RelatedPostings): _FIELDS: Dict[str, Callable[[data.Posting], MetaValue]] = { 'account': operator.attrgetter('account'), 'contract': _meta_getter('contract'), - 'cost': operator.attrgetter('cost'), 'entity': _meta_getter('entity'), 'invoice': _meta_getter('invoice'), 'purchase_order': _meta_getter('purchase-order'), @@ -157,8 +156,6 @@ class AccrualPostings(core.RelatedPostings): 'accounts', 'contract', 'contracts', - 'cost', - 'costs', 'entity', 'entitys', 'entities', @@ -226,6 +223,17 @@ class AccrualPostings(core.RelatedPostings): get_func(post), ) yield Error(post.meta, errmsg, post.meta.txn) + costs = collections.defaultdict(set) + for post in self: + costs[post.units.currency].add(post.cost) + for code, currency_costs in costs.items(): + if len(currency_costs) > 1: + for post in self: + if post.units.currency == code: + errmsg = 'inconsistent cost for invoice {}: {}'.format( + self.invoice or "", post.cost, + ) + yield Error(post.meta, errmsg, post.meta.txn) def is_paid(self, default: Optional[bool]=None) -> Optional[bool]: if self.accrual_type is None: diff --git a/tests/books/accruals.beancount b/tests/books/accruals.beancount index d538be0..282e399 100644 --- a/tests/books/accruals.beancount +++ b/tests/books/accruals.beancount @@ -41,8 +41,8 @@ rt-id: "rt:505" invoice: "rt:505/5050" approval: "rt:505/5040" - Income:Donations -1000.00 USD - Assets:Receivable:Accounts 1000.00 USD + Income:Donations -2,500 EUR {1.100 USD} + Assets:Receivable:Accounts 2,500 EUR {1.100 USD} 2020-05-10 * "Lawyer" "April legal services" rt-id: "rt:510" @@ -60,8 +60,8 @@ 2020-05-20 * "DonorA" "Donation made" rt-id: "rt:505" invoice: "rt:505/5050" - Assets:Receivable:Accounts -1000.00 USD - Assets:Checking 1000.00 USD + Assets:Receivable:Accounts -2,750.00 USD + Assets:Checking 2,750.00 USD receipt: "DonorAWire.pdf" 2020-05-25 * "Lawyer" "May payment" diff --git a/tests/test_reports_accrual.py b/tests/test_reports_accrual.py index bd64fc6..5493e2c 100644 --- a/tests/test_reports_accrual.py +++ b/tests/test_reports_accrual.py @@ -212,20 +212,6 @@ def test_accrual_postings_consistent_account(acct_name): assert related.account == acct_name assert related.accounts == {acct_name} -@pytest.mark.parametrize('cost', [ - testutil.Cost('1.2', 'USD'), - None, -]) -def test_accrual_postings_consistent_cost(cost): - meta = {'invoice': 'FXinvoice.pdf'} - txn = testutil.Transaction(postings=[ - (ACCOUNTS[0], 60, 'EUR', cost, meta), - (ACCOUNTS[0], 30, 'EUR', cost, meta), - ]) - related = accrual.AccrualPostings(data.Posting.from_txn(txn)) - assert related.cost == cost - assert related.costs == {cost} - @pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values( CONSISTENT_METADATA, ACCOUNTS, @@ -255,20 +241,6 @@ def test_accrual_postings_inconsistent_account(): assert related.account is related.INCONSISTENT assert related.accounts == set(ACCOUNTS) -def test_accrual_postings_inconsistent_cost(): - meta = {'invoice': 'FXinvoice.pdf'} - costs = { - testutil.Cost('1.1', 'USD'), - testutil.Cost('1.2', 'USD'), - } - txn = testutil.Transaction(postings=[ - (ACCOUNTS[0], 10, 'EUR', cost, meta) - for cost in costs - ]) - related = accrual.AccrualPostings(data.Posting.from_txn(txn)) - assert related.cost is related.INCONSISTENT - assert related.costs == costs - @pytest.mark.parametrize('meta_key,acct_name', testutil.combine_values( CONSISTENT_METADATA, ACCOUNTS,