diff --git a/tests/test_reports_accrual.py b/tests/test_reports_accrual.py index 3a16dfc3105b9f5fb9a3bdca5dca02bcd4a71da2..9b14372a419c3c84d05cf26b00427e12965c73cd 100644 --- a/tests/test_reports_accrual.py +++ b/tests/test_reports_accrual.py @@ -20,6 +20,7 @@ import datetime import io import itertools import logging +import operator import re import babel.numbers @@ -478,18 +479,21 @@ def test_consistency_check_cost(): assert err.source.get('lineno') == post.meta['lineno'] def test_make_consistent_not_needed(): - invoice = 'Invoices/ConsistentDoc.pdf' + main_meta = { + 'entity': 'ConsistentTest', + 'invoice': 'Invoices/ConsistentDoc.pdf', + } other_meta = {key: f'{key}.pdf' for key in CONSISTENT_METADATA} # We intentionally make inconsistencies in "minor" metadata that shouldn't # split out the group. txn = testutil.Transaction(postings=[ - (ACCOUNTS[0], 20, {**other_meta, 'invoice': invoice}), - (ACCOUNTS[0], 25, {'invoice': invoice}), + (ACCOUNTS[0], 20, {**main_meta, **other_meta}), + (ACCOUNTS[0], 25, {**main_meta}), ]) related = accrual.AccrualPostings(data.Posting.from_txn(txn)) consistent = related.make_consistent() actual_key, actual_postings = next(consistent) - assert actual_key == invoice + assert actual_key == main_meta['invoice'] assert actual_postings is related assert next(consistent, None) is None @@ -500,13 +504,17 @@ def test_make_consistent_not_needed(): )) def test_make_consistent_bad_invoice(acct_name, invoice, day): txn = testutil.Transaction(date=datetime.date(2019, 1, day), postings=[ - (acct_name, index * 10, {'invoice': invoice}) + (acct_name, index * 10, {'invoice': invoice, 'entity': f'BadInvoice{day}'}) for index in range(1, 4) ]) related = accrual.AccrualPostings(data.Posting.from_txn(txn)) consistent = dict(related.make_consistent()) assert len(consistent) == 1 - actual = consistent.get(f'{acct_name} None {invoice}') + key = next(iter(consistent)) + assert acct_name in key + if invoice: + assert str(invoice) in key + actual = consistent[key] assert actual assert len(actual) == 3 for act_post, exp_post in zip(actual, txn.postings): @@ -516,16 +524,15 @@ def test_make_consistent_bad_invoice(acct_name, invoice, day): def test_make_consistent_across_accounts(): invoice = 'Invoices/CrossAccount.pdf' txn = testutil.Transaction(date=datetime.date(2019, 2, 1), postings=[ - (acct_name, 100, {'invoice': invoice}) + (acct_name, 100, {'invoice': invoice, 'entity': 'CrossAccount'}) for acct_name in ACCOUNTS ]) related = accrual.AccrualPostings(data.Posting.from_txn(txn)) consistent = dict(related.make_consistent()) assert len(consistent) == len(ACCOUNTS) - for acct_name in ACCOUNTS: - actual = consistent[f'{invoice} {acct_name}'] - assert len(actual) == 1 - assert actual[0].account == acct_name + for key, posts in consistent.items(): + assert len(posts) == 1 + assert posts.account in key def test_make_consistent_both_invoice_and_account(): txn = testutil.Transaction(date=datetime.date(2019, 2, 2), postings=[ @@ -534,10 +541,39 @@ def test_make_consistent_both_invoice_and_account(): related = accrual.AccrualPostings(data.Posting.from_txn(txn)) consistent = dict(related.make_consistent()) assert len(consistent) == len(ACCOUNTS) - for acct_name in ACCOUNTS: - actual = consistent[f'{acct_name} None None'] - assert len(actual) == 1 - assert actual[0].account == acct_name + for key, posts in consistent.items(): + assert len(posts) == 1 + assert posts.account in key + +@pytest.mark.parametrize('acct_name', ACCOUNTS) +def test_make_consistent_across_entity(acct_name): + amt_sign = operator.pos if acct_name.startswith('Assets') else operator.neg + txn = testutil.Transaction(postings=[ + (acct_name, amt_sign(n), {'invoice': 'Inv/1.pdf', 'entity': f'Entity{n}'}) + for n in range(1, 4) + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + consistent = dict(related.make_consistent()) + assert len(consistent) == 3 + for key, posts in consistent.items(): + assert len(posts) == 1 + assert len(posts.accrued_entities) == 1 + assert next(posts.entities()) in key + +@pytest.mark.parametrize('acct_name', ACCOUNTS) +def test_make_consistent_entity_differs_accrual_payment(acct_name): + invoice = 'Invoices/DifferPay.pdf' + txn = testutil.Transaction(postings=[ + # Depending on the account, the order of the accrual and payment might + # be swapped here, but that shouldn't matter. + (acct_name, 125, {'invoice': invoice, 'entity': 'Positive'}), + (acct_name, -125, {'invoice': invoice, 'entity': 'Negative'}), + ]) + related = accrual.AccrualPostings(data.Posting.from_txn(txn)) + consistent = related.make_consistent() + _, actual = next(consistent) + assert actual is related + assert next(consistent, None) is None def check_output(output, expect_patterns): output.seek(0) @@ -659,6 +695,17 @@ def test_aging_report_date_cutoffs(accrual_postings, date, recv_end, pay_end): output = run_aging_report(accrual_postings, date) check_aging_ods(output, date, expect_recv, expect_pay) +def test_aging_report_entity_consistency(accrual_postings): + output = run_aging_report(( + post for post in accrual_postings + if post.meta.get('rt-id') == 'rt:480' + and post.units.number < 0 + )) + check_aging_ods(output, None, [], [ + AgingRow.make_simple('2010-04-15', 'MultiPartyA', 125, 'rt:480/4800'), + AgingRow.make_simple('2010-04-15', 'MultiPartyB', 125, 'rt:480/4800'), + ]) + def run_main(arglist, config=None): if config is None: config = testutil.TestConfig(