From c498589c78b308ddfb339f5030e2402bae8f9fd5 Mon Sep 17 00:00:00 2001 From: fabrepe Date: Thu, 28 Feb 2019 11:03:59 +0100 Subject: [PATCH 1/2] Multi account OFX support * Fix #32 * Allows specify format of account in case of multiple accounts when importing OFX --- ledgerautosync/cli.py | 77 +++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 28 deletions(-) diff --git a/ledgerautosync/cli.py b/ledgerautosync/cli.py index 303dd46..519cb4c 100755 --- a/ledgerautosync/cli.py +++ b/ledgerautosync/cli.py @@ -55,7 +55,7 @@ def find_ledger_file(ledgerrcpath=None): return None -def print_results(converter, ofx, ledger, txns, args): +def print_results(converter, ofx, account_idx, ledger, txns, args): """ This function is the final common pathway of program: @@ -69,18 +69,18 @@ def print_results(converter, ofx, ledger, txns, args): if (not(ledger.check_transaction_by_id ("ofxid", converter.mk_ofxid(AUTOSYNC_INITIAL))) and not(ledger.check_transaction_by_id("ofxid", ALL_AUTOSYNC_INITIAL))): - print(converter.format_initial_balance(ofx.account.statement)) + print(converter.format_initial_balance(ofx.accounts[account_idx].statement)) for txn in txns: print(converter.convert(txn).format(args.indent)) if args.assertions: - print(converter.format_balance(ofx.account.statement)) + print(converter.format_balance(ofx.accounts[account_idx].statement)) # if OFX has positions use these to obtain commodity prices # and print "P" records to provide dated/timed valuations # Note that this outputs only the commodity price, # not your position (e.g. # shares), even though this is in the OFX record - if hasattr(ofx.account.statement, 'positions'): - for pos in ofx.account.statement.positions: + if hasattr(ofx.accounts[account_idx].statement, 'positions'): + for pos in ofx.accounts[account_idx].statement.positions: print(converter.format_position(pos)) @@ -112,31 +112,44 @@ def import_ofx(ledger, args): sync = OfxSynchronizer(ledger, hardcodeaccount=args.hardcodeaccount, shortenaccount=args.shortenaccount) ofx = OfxSynchronizer.parse_file(args.PATH) - txns = sync.filter( - ofx.account.statement.transactions, - ofx.account.account_id) - accountname = args.account - if accountname is None: - if ofx.account.institution is not None: - accountname = "%s:%s" % (ofx.account.institution.organization, - ofx.account.account_id) - else: - accountname = UNKNOWN_BANK_ACCOUNT - # build SecurityList (including indexing by CUSIP and ticker symbol) - security_list = SecurityList(ofx) + account_idx = 0 + for account in ofx.accounts: + txns = sync.filter( + account.statement.transactions, + account.account_id) + try: + accountname = args.account[account_idx] + except IndexError: + accountname = None + if accountname is None: + if account.institution is not None: + accountname = "%s:%s" % (account.institution.organization, + account.account_id) + elif args.account_format is not None: + accountname = args.account_format.format( + account_id=account.account_id, + account_type=account.account_type, + routing_number=account.routing_number, + branch_id=account.branch_id) + else: + accountname = "%s:%s" % (UNKNOWN_BANK_ACCOUNT, account_idx) + + # build SecurityList (including indexing by CUSIP and ticker symbol) + security_list = SecurityList(ofx) - converter = OfxConverter(account=ofx.account, - name=accountname, - ledger=ledger, - indent=args.indent, - fid=args.fid, - unknownaccount=args.unknownaccount, - payee_format=args.payee_format, - hardcodeaccount=args.hardcodeaccount, - shortenaccount=args.shortenaccount, - security_list=security_list) - print_results(converter, ofx, ledger, txns, args) + converter = OfxConverter(account=account, + name=accountname, + ledger=ledger, + indent=args.indent, + fid=args.fid, + unknownaccount=args.unknownaccount, + payee_format=args.payee_format, + hardcodeaccount=args.hardcodeaccount, #TODO + shortenaccount=args.shortenaccount, #TODO + security_list=security_list) + print_results(converter, ofx, account_idx, ledger, txns, args) + account_idx += 1 def import_csv(ledger, args): @@ -233,6 +246,14 @@ def run(args=None, config=None): {tferaction} for OFX. If the input file is a CSV file, substitutions are written using the CSV file column names between {}.""") + parser.add_argument( + '--account-format', + type=str, + default=None, + dest='account_format', + help="""Format string to use for generating the account line. + Substitutions can be written using {account_id}, {routing_number}, + {branch_id}, {account_type} for OFX.""") parser.add_argument('--python', action='store_true', default=False, help='use the ledger python interface') parser.add_argument('--slow', action='store_true', default=False, From f49b7430dad4f101667b5a858e9d8285e900212c Mon Sep 17 00:00:00 2001 From: fabrepe Date: Fri, 1 Mar 2019 12:12:39 +0100 Subject: [PATCH 2/2] FIX print_results and --account errors * FIX print_results wrong number of arguments error when called by sync * fix typos in --account argument to allow forcing the account name when importing OFX files with multiple accounts --- ledgerautosync/cli.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/ledgerautosync/cli.py b/ledgerautosync/cli.py index 519cb4c..531d6bb 100755 --- a/ledgerautosync/cli.py +++ b/ledgerautosync/cli.py @@ -55,7 +55,7 @@ def find_ledger_file(ledgerrcpath=None): return None -def print_results(converter, ofx, account_idx, ledger, txns, args): +def print_results(converter, ofx, ledger, txns, args, account_idx=0): """ This function is the final common pathway of program: @@ -113,6 +113,10 @@ def import_ofx(ledger, args): shortenaccount=args.shortenaccount) ofx = OfxSynchronizer.parse_file(args.PATH) + if args.account != None and len(args.account) != len(ofx.accounts): + sys.stderr.write("number of account name (--account) does not match the number of accounts in the OFX file\n") + exit(1) + account_idx = 0 for account in ofx.accounts: txns = sync.filter( @@ -120,7 +124,7 @@ def import_ofx(ledger, args): account.account_id) try: accountname = args.account[account_idx] - except IndexError: + except (IndexError, TypeError): accountname = None if accountname is None: if account.institution is not None: @@ -148,7 +152,7 @@ def import_ofx(ledger, args): hardcodeaccount=args.hardcodeaccount, #TODO shortenaccount=args.shortenaccount, #TODO security_list=security_list) - print_results(converter, ofx, account_idx, ledger, txns, args) + print_results(converter, ofx, ledger, txns, args, account_idx) account_idx += 1 @@ -192,7 +196,7 @@ def run(args=None, config=None): help='do not stop until max days reached') parser.add_argument('PATH', nargs='?', help='do not sync; import from OFX \ file') - parser.add_argument('-a', '--account', type=str, default=None, + parser.add_argument('-a', '--account', type=str, default=None, action='append', help='sync only the named account; \ if importing from file, set account name for import') parser.add_argument('-l', '--ledger', type=str, default=None,