reconciler: Handle comma separators in some FR statements
This commit is contained in:
parent
82ec68962a
commit
0968f7f051
2 changed files with 54 additions and 8 deletions
|
@ -194,6 +194,11 @@ def read_transactions_from_csv(f: TextIO, standardize_statement_record: Callable
|
||||||
return sort_records([standardize_statement_record(row, i) for i, row in enumerate(reader, 2)])
|
return sort_records([standardize_statement_record(row, i) for i, row in enumerate(reader, 2)])
|
||||||
|
|
||||||
|
|
||||||
|
def parse_amount(amount: str) -> decimal.Decimal:
|
||||||
|
"""Parse amounts and handle comma separators as seen in some FR statements."""
|
||||||
|
return decimal.Decimal(amount.replace(',', ''))
|
||||||
|
|
||||||
|
|
||||||
def validate_amex_csv(sample: str, account: str) -> None:
|
def validate_amex_csv(sample: str, account: str) -> None:
|
||||||
required_cols = {'Date', 'Amount', 'Description', 'Card Member'}
|
required_cols = {'Date', 'Amount', 'Description', 'Card Member'}
|
||||||
reader = csv.DictReader(io.StringIO(sample))
|
reader = csv.DictReader(io.StringIO(sample))
|
||||||
|
@ -206,7 +211,7 @@ def standardize_amex_record(row: Dict, line: int) -> Dict:
|
||||||
# NOTE: Statement doesn't seem to give us a running balance or a final total.
|
# NOTE: Statement doesn't seem to give us a running balance or a final total.
|
||||||
return {
|
return {
|
||||||
'date': datetime.datetime.strptime(row['Date'], '%m/%d/%Y').date(),
|
'date': datetime.datetime.strptime(row['Date'], '%m/%d/%Y').date(),
|
||||||
'amount': -1 * decimal.Decimal(row['Amount']),
|
'amount': -1 * parse_amount(row['Amount']),
|
||||||
# Descriptions have too much noise, so taking just the start
|
# Descriptions have too much noise, so taking just the start
|
||||||
# significantly assists the fuzzy matching.
|
# significantly assists the fuzzy matching.
|
||||||
'payee': remove_payee_junk(row['Description'] or '')[:20],
|
'payee': remove_payee_junk(row['Description'] or '')[:20],
|
||||||
|
@ -225,7 +230,7 @@ def validate_fr_csv(sample: str, account: str) -> None:
|
||||||
def standardize_fr_record(row: Dict, line: int) -> Dict:
|
def standardize_fr_record(row: Dict, line: int) -> Dict:
|
||||||
return {
|
return {
|
||||||
'date': datetime.datetime.strptime(row['Date'], '%m/%d/%Y').date(),
|
'date': datetime.datetime.strptime(row['Date'], '%m/%d/%Y').date(),
|
||||||
'amount': decimal.Decimal(row['Amount']),
|
'amount': parse_amount(row['Amount']),
|
||||||
'payee': remove_payee_junk(row['Detail'] or '')[:20],
|
'payee': remove_payee_junk(row['Detail'] or '')[:20],
|
||||||
'check_id': row['Serial Num'].lstrip('0'),
|
'check_id': row['Serial Num'].lstrip('0'),
|
||||||
'line': line,
|
'line': line,
|
||||||
|
|
|
@ -5,15 +5,17 @@ import tempfile
|
||||||
import textwrap
|
import textwrap
|
||||||
|
|
||||||
from conservancy_beancount.reconcile.statement_reconciler import (
|
from conservancy_beancount.reconcile.statement_reconciler import (
|
||||||
match_statement_and_books,
|
|
||||||
remove_payee_junk,
|
|
||||||
date_proximity,
|
date_proximity,
|
||||||
remove_duplicate_words,
|
match_statement_and_books,
|
||||||
payee_match,
|
|
||||||
metadata_for_match,
|
metadata_for_match,
|
||||||
write_metadata_to_books,
|
payee_match,
|
||||||
totals,
|
remove_duplicate_words,
|
||||||
|
remove_payee_junk,
|
||||||
|
standardize_amex_record,
|
||||||
|
standardize_fr_record,
|
||||||
subset_match,
|
subset_match,
|
||||||
|
totals,
|
||||||
|
write_metadata_to_books,
|
||||||
)
|
)
|
||||||
|
|
||||||
# These data structures represent individual transactions as taken from the
|
# These data structures represent individual transactions as taken from the
|
||||||
|
@ -341,3 +343,42 @@ def test_subset_passes_through_all_non_matches():
|
||||||
[S1], # No match: preserved intact
|
[S1], # No match: preserved intact
|
||||||
[B2, B3_next_day, B3_next_week] # No match: preserved intact
|
[B2, B3_next_day, B3_next_week] # No match: preserved intact
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_fr_record_with_comma_separators():
|
||||||
|
# CSV would look something like:
|
||||||
|
#
|
||||||
|
# "Date","ABA Num","Currency","Account Num","Account Name","Description","BAI Code","Amount","Serial Num","Ref Num","Detail"
|
||||||
|
# "02/07/2022",,,,,,,"10,000.00",,,"XXXX"
|
||||||
|
input_row = {
|
||||||
|
'Date': '02/07/2022',
|
||||||
|
'Amount': '10,000.00',
|
||||||
|
'Detail': 'XXXX',
|
||||||
|
'Serial Num': '',
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'date': datetime.date(2022, 2, 7),
|
||||||
|
'amount': decimal.Decimal('10000'),
|
||||||
|
'payee': 'XXXX',
|
||||||
|
'check_id': '',
|
||||||
|
'line': 1,
|
||||||
|
}
|
||||||
|
assert standardize_fr_record(input_row, line=1) == expected
|
||||||
|
|
||||||
|
|
||||||
|
def test_handles_amex_record_with_comma_separators():
|
||||||
|
# This insn't typically a problem with AMEX, but adding for completeness.
|
||||||
|
input_row = {
|
||||||
|
'Date': '02/07/2022',
|
||||||
|
'Amount': '-10,000.00', # Amounts are from Bank's perspective/negated.
|
||||||
|
'Description': 'XXXX',
|
||||||
|
'Serial Num': '',
|
||||||
|
}
|
||||||
|
expected = {
|
||||||
|
'date': datetime.date(2022, 2, 7),
|
||||||
|
'amount': decimal.Decimal('10000'),
|
||||||
|
'payee': 'XXXX',
|
||||||
|
'check_id': '',
|
||||||
|
'line': 1,
|
||||||
|
}
|
||||||
|
assert standardize_amex_record(input_row, line=1) == expected
|
||||||
|
|
Loading…
Reference in a new issue