From 0caf78436fe9288bc83653bbfa84d4622d985c2b 2020-06-17 19:32:08 From: Brett Smith Date: 2020-06-17 19:32:08 Subject: [PATCH] accrual: Generate an aging report in more cases. Default to generating an aging report unless the user searched for a specific RT ticket or invoice. --- diff --git a/conservancy_beancount/reports/accrual.py b/conservancy_beancount/reports/accrual.py index 2a4dec933ed1bf6056a5bd798c0834e47c4dd56e..b116a9969204a929fc3b2df5fc770da6f8519304 100644 --- a/conservancy_beancount/reports/accrual.py +++ b/conservancy_beancount/reports/accrual.py @@ -6,8 +6,7 @@ Liabilities:Payable) for errors and metadata consistency, and reports any problems on stderr. Then it writes a report about the status of those accruals. -If you run it with no arguments, it will generate an aging report in ODS format -in the current directory. +If you run it with no arguments, it will generate an aging report in ODS format. Otherwise, the typical way to run it is to pass an RT ticket number or invoice link as an argument, to report about accruals that match those @@ -38,17 +37,19 @@ You can pass any number of search terms. For example:: accrual-report 1230 entity=Doe-Jane accrual-report will automatically decide what kind of report to generate -from the search terms you provide and the results they return. If you pass -no search terms, it generates an aging report. If your search terms match a -single outstanding payable, it writes an outgoing approval report. -Otherwise, it writes a basic balance report. You can specify what report +from the search terms you provide and the results they return. If you +searched on an RT ticket or invoice that returned a single outstanding +payable, it writes an outgoing approval report. If you searched on RT ticket +or invoice that returned other results, it writes a balance +report. Otherwise, it writes an aging report. You can specify what report type you want with the ``--report-type`` option:: - # Write an outgoing approval report for all outstanding accruals for + # Write an outgoing approval report for all outstanding payables for # Jane Doe, even if there's more than one accrual-report --report-type outgoing entity=Doe-Jane - # Write an aging report for a specific project - accrual-report --report-type aging project=ProjectName + # Write an aging report for a single RT invoice (this can be helpful when + # one invoice covers multiple parties) + accrual-report --report-type aging 12/345 """ # Copyright © 2020 Brett Smith # @@ -627,7 +628,10 @@ metadata to match. A single ticket number is a shortcut for `TIK/ATT` format, is a shortcut for `invoice=LINK`. """) args = parser.parse_args(arglist) - if args.report_type is None and not args.search_terms: + if args.report_type is None and not any( + term.meta_key == 'invoice' or term.meta_key == 'rt-id' + for term in args.search_terms + ): args.report_type = ReportType.AGING return args diff --git a/tests/test_reports_accrual.py b/tests/test_reports_accrual.py index 2ca92b44de0b591fd9e7af08a1ba2bbc080f54d8..f99706c6276d5216e1ab5b36ff3462a80249ef89 100644 --- a/tests/test_reports_accrual.py +++ b/tests/test_reports_accrual.py @@ -577,15 +577,19 @@ def test_aging_report_does_not_include_too_recent_postings(accrual_postings): project='Development Grant'), ], []) -def run_main(arglist, config=None): +def run_main(arglist, config=None, out_type=io.StringIO): if config is None: config = testutil.TestConfig( books_path=testutil.test_path('books/accruals.beancount'), rt_client=RTClient(), ) - output = io.StringIO() + if out_type is io.BytesIO: + arglist.insert(0, '--output-file=-') + output = out_type() errors = io.StringIO() retcode = accrual.main(arglist, output, errors, config) + output.seek(0) + errors.seek(0) return retcode, output, errors def check_main_fails(arglist, config, error_flags): @@ -593,7 +597,6 @@ def check_main_fails(arglist, config, error_flags): assert retcode > 16 assert (retcode - 16) & error_flags assert not output.getvalue() - errors.seek(0) return errors @pytest.mark.parametrize('arglist', [ @@ -624,7 +627,7 @@ def test_output_payments_when_only_match(arglist, expect_invoice): @pytest.mark.parametrize('arglist,expect_amount', [ (['310'], 420), (['310/3120'], 220), - (['entity=Vendor'], 420), + (['-t', 'out', 'entity=Vendor'], 420), ]) def test_main_outgoing_report(arglist, expect_amount): retcode, output, errors = run_main(arglist) @@ -643,7 +646,6 @@ def test_main_outgoing_report(arglist, expect_amount): @pytest.mark.parametrize('arglist', [ ['-t', 'balance'], ['515/5150'], - ['entity=MatchingProgram'], ]) def test_main_balance_report(arglist): retcode, output, errors = run_main(arglist) @@ -666,23 +668,19 @@ def test_main_balance_report_because_no_rt_id(): @pytest.mark.parametrize('arglist', [ [], - ['-t', 'aging', 'entity=Lawyer'], + ['entity=Lawyer'], ]) -def test_main_aging_report(tmp_path, arglist): +def test_main_aging_report(arglist): if arglist: recv_rows = [row for row in AGING_AR if 'Lawyer' in row.entity] pay_rows = [row for row in AGING_AP if 'Lawyer' in row.entity] else: recv_rows = AGING_AR pay_rows = AGING_AP - output_path = tmp_path / 'AgingReport.ods' - arglist.insert(0, f'--output-file={output_path}') - retcode, output, errors = run_main(arglist) + retcode, output, errors = run_main(arglist, out_type=io.BytesIO) assert not errors.getvalue() assert retcode == 0 - assert not output.getvalue() - with output_path.open('rb') as ods_file: - check_aging_ods(ods_file, None, recv_rows, pay_rows) + check_aging_ods(output, None, recv_rows, pay_rows) def test_main_no_books(): errors = check_main_fails([], testutil.TestConfig(), 1 | 8) @@ -693,7 +691,7 @@ def test_main_no_books(): @pytest.mark.parametrize('arglist', [ ['499'], ['505/99999'], - ['entity=NonExistent'], + ['-t', 'balance', 'entity=NonExistent'], ]) def test_main_no_matches(arglist, caplog): check_main_fails(arglist, None, 8)