Changeset - e764f3d0ef05
[Not reviewed]
0 2 0
Ben Sturmfels (bsturmfels) - 16 months ago 2023-02-11 04:23:15
ben@sturm.com.au
reconcile: Factor out the output formatting
2 files changed with 28 insertions and 13 deletions:
0 comments (0 inline, 0 general)
conservancy_beancount/reconcile/statement_reconciler.py
Show inline comments
...
 
@@ -616,24 +616,42 @@ def totals(matches: List[Tuple[List, List, List]]) -> Tuple[decimal.Decimal, dec
 

	
 

	
 
def process_unmatched(statement_trans: List[dict], books_trans: List[dict]) -> List[Tuple[List, List, List]]:
 
    """Format the remaining unmatched transactions to be added to one single list of matches."""
 
    matches: List[Tuple[List, List, List]] = []
 
    for r1 in statement_trans:
 
        matches.append(([r1], [], ['no match']))
 
    for r2 in books_trans:
 
        matches.append(([], [r2], ['no match']))
 
    return matches
 

	
 

	
 
def format_output(matches, begin_date, end_date, csv_statement, show_reconciled_matches) -> str:
 
    with io.StringIO() as out:
 
        match_output = format_matches(matches, csv_statement, show_reconciled_matches)
 
        _, total_missing_from_books, total_missing_from_statement = totals(matches)
 
        print('-' * 155, file=out)
 
        statement_heading = f'Statement transactions {begin_date} to {end_date}'
 
        print(f'{statement_heading:<52}            {"Books transactions":<58}   Notes', file=out)
 
        print('-' * 155, file=out)
 
        for _, output in sorted(match_output, key=lambda x: x[0]):
 
            print(output, file=out)
 
        print('-' * 155, file=out)
 
        print(f'Sub-total not on statement: {total_missing_from_statement:12,.2f}', file=out)
 
        print(f'Sub-total not in books:     {total_missing_from_books:12,.2f}', file=out)
 
        print(f'Total:                      {total_missing_from_statement + total_missing_from_books:12,.2f}', file=out)
 
        print('-' * 155, file=out)
 
        return out.getvalue()
 

	
 

	
 
def main(arglist: Optional[Sequence[str]] = None,
 
         stdout: TextIO = sys.stdout,
 
         stderr: TextIO = sys.stderr,
 
         config: Optional[configmod.Config] = None,
 
         ) -> int:
 
    args = parse_arguments(arglist)
 
    cliutil.set_loglevel(logger, args.loglevel)
 
    if config is None:
 
        config = configmod.Config()
 
        config.load_file()
 

	
 
    # Validate and normalise the statement into our standard
...
 
@@ -680,37 +698,25 @@ def main(arglist: Optional[Sequence[str]] = None,
 
    # Apply two passes of matching, one for standard matches and one
 
    # for subset matches.
 
    matches, remaining_statement_trans, remaining_books_trans = match_statement_and_books(statement_trans, books_trans)
 
    subset_matches, remaining_statement_trans, remaining_books_trans = subset_match(
 
        remaining_statement_trans, remaining_books_trans)
 
    matches.extend(subset_matches)
 

	
 
    # Add the remaining unmatched to make one big list of matches, successful or not.
 
    unmatched = process_unmatched(remaining_statement_trans, remaining_books_trans)
 
    matches.extend(unmatched)
 

	
 
    # Print out results of our matching.
 
    match_output = format_matches(matches, args.csv_statement, args.show_reconciled_matches)
 
    _, total_missing_from_books, total_missing_from_statement = totals(matches)
 
    print('-' * 155)
 
    statement_heading = f'Statement transactions {begin_date} to {end_date}'
 
    print(f'{statement_heading:<52}            {"Books transactions":<58}   Notes')
 
    print('-' * 155)
 
    for _, output in sorted(match_output, key=lambda x: x[0]):
 
        print(output)
 
    print('-' * 155)
 
    print(f'Sub-total not on statement: {total_missing_from_statement:12,.2f}')
 
    print(f'Sub-total not in books:     {total_missing_from_books:12,.2f}')
 
    print(f'Total:                      {total_missing_from_statement + total_missing_from_books:12,.2f}')
 
    print('-' * 155)
 
    print(format_output(matches, begin_date, end_date, args.csv_statement, args.show_reconciled_matches))
 

	
 
    # Write statement metadata back to the books.
 
    metadata_to_apply = []
 
    for match in matches:
 
        metadata_to_apply.extend(metadata_for_match(match, args.bank_statement, args.csv_statement))
 
    if metadata_to_apply and not args.non_interactive:
 
        print('Mark matched transactions as reconciled in the books? (y/N) ', end='')
 
        if input().lower() == 'y':
 
            write_metadata_to_books(metadata_to_apply)
 

	
 

	
 
entry_point = cliutil.make_entry_point(__name__, PROGNAME)
tests/test_reconcile.py
Show inline comments
 
import datetime
 
import decimal
 
import io
 
import os
 
import tempfile
 
import textwrap
 

	
 
from conservancy_beancount.reconcile.statement_reconciler import (
 
    date_proximity,
 
    format_output,
 
    match_statement_and_books,
 
    metadata_for_match,
 
    payee_match,
 
    read_amex_csv,
 
    read_fr_csv,
 
    remove_duplicate_words,
 
    remove_payee_junk,
 
    subset_match,
 
    totals,
 
    write_metadata_to_books,
 
)
 

	
...
 
@@ -370,12 +371,20 @@ def test_handles_fr_csv():
 
            'check_id': '',
 
            'line': 2,
 
        },
 
        {
 
            'date': datetime.date(2022, 4, 18),
 
            'amount': decimal.Decimal('-4.50'),
 
            'payee': '',
 
            'check_id': '3741',
 
            'line': 3,
 
        },
 
    ]
 
    assert read_fr_csv(io.StringIO(CSV)) == expected
 

	
 

	
 
def test_format_output():
 
    statement = [S1]
 
    books = [B1]
 
    matches, _, _ = match_statement_and_books(statement, books)
 
    output = format_output(matches, datetime.date(2022, 1, 1), datetime.date(2022, 2, 1), 'test.csv', True)
 
    assert '2022-01-01:       10.00 Patreon         / Patreon   / 12345  →  2022-01-01:       10.00 Patreon                              ✓ Matched' in output
0 comments (0 inline, 0 general)