def test_parses_correct_time(self): '''Test whether it can parse correct time for some valid time fields''' self.assertEquals(OfxParser.parseOfxDateTime('19881201'), datetime(1988, 12, 1, 0, 0)) self.assertEquals(OfxParser.parseOfxDateTime('19881201230100'), datetime(1988, 12, 1, 23, 1)) self.assertEquals(OfxParser.parseOfxDateTime('20120229230100'), datetime(2012, 2, 29, 23, 1))
def download_parsed(self, days=60): """Downloaded OFX response parsed by :py:meth:`OfxParser.parse` :param days: Number of days to look back at :type days: integer :rtype: :py:class:`ofxparser.Ofx` """ if IS_PYTHON_2: return OfxParser.parse(self.download(days=days)) else: return OfxParser.parse(BytesIO((((self.download(days=days)).read()).encode())))
def testDecimalParsingWithCommas(self): # open files ofx_standard = OfxParser.parse(open_file('bank_medium.ofx')) ofx_w_commas = OfxParser.parse(open_file('bank_medium_with_commas.ofx')) # extract transactions t1 = list(t.amount for t in ofx_standard.account.statement.transactions) t2 = list(t.amount for t in ofx_w_commas.account.statement.transactions) # compare self.assertEquals(t1, t2)
def testSuccess(self): ofx = OfxParser.parse(open_file("signon_success.ofx"), True) self.assertTrue(ofx.signon.success) self.assertEquals(ofx.signon.code, 0) self.assertEquals(ofx.signon.severity, "INFO") self.assertEquals(ofx.signon.message, "Login successful") ofx = OfxParser.parse(open_file("signon_success_no_message.ofx"), True) self.assertTrue(ofx.signon.success) self.assertEquals(ofx.signon.code, 0) self.assertEquals(ofx.signon.severity, "INFO") self.assertEquals(ofx.signon.message, "")
def testSuccess(self): ofx = OfxParser.parse(open_file('signon_success.ofx'), True) self.assertTrue(ofx.signon.success) self.assertEquals(ofx.signon.code, 0) self.assertEquals(ofx.signon.severity, 'INFO') self.assertEquals(ofx.signon.message, 'Login successful') ofx = OfxParser.parse(open_file('signon_success_no_message.ofx'), True) self.assertTrue(ofx.signon.success) self.assertEquals(ofx.signon.code, 0) self.assertEquals(ofx.signon.severity, 'INFO') self.assertEquals(ofx.signon.message, '')
def testFailure(self): with open_file('signon_fail.ofx') as f: ofx = OfxParser.parse(f, True) self.assertFalse(ofx.signon.success) self.assertEqual(ofx.signon.code, 15500) self.assertEqual(ofx.signon.severity, 'ERROR') self.assertEqual(ofx.signon.message, 'Your request could not be processed because you supplied an invalid identification code or your password was incorrect')
def testEverything(self): ofx = OfxParser.parse(open_file('bank_medium.ofx')) self.assertEquals('12300 000012345678', ofx.account.number) self.assertEquals('160000100', ofx.account.routing_number) self.assertEquals('00', ofx.account.branch_id) self.assertEquals('CHECKING', ofx.account.account_type) self.assertEquals(Decimal('382.34'), ofx.account.statement.balance) self.assertEquals(datetime(2009, 5, 23, 12, 20, 17), ofx.account.statement.balance_date) # Todo: support values in decimal or int form. # self.assertEquals('15', # ofx.bank_account.statement.balance_in_pennies) self.assertEquals( Decimal('682.34'), ofx.account.statement.available_balance) self.assertEquals(datetime(2009, 5, 23, 12, 20, 17), ofx.account.statement.available_balance_date) self.assertEquals( datetime(2009, 4, 1), ofx.account.statement.start_date) self.assertEquals( datetime(2009, 5, 23, 12, 20, 17), ofx.account.statement.end_date) self.assertEquals(3, len(ofx.account.statement.transactions)) transaction = ofx.account.statement.transactions[0] self.assertEquals("MCDONALD'S #112", transaction.payee) self.assertEquals('pos', transaction.type) self.assertEquals(Decimal('-6.60'), transaction.amount)
def test_checking(self): ofx = OfxParser.parse(open(os.path.join('fixtures', 'checking.ofx'))) converter = OfxConverter(account=ofx.account, name="Foo") self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[0]).format(), """2011/03/31 DIVIDEND EARNED FOR PERIOD OF 03/01/2011 THROUGH 03/31/2011 ANNUAL PERCENTAGE YIELD EARNED IS 0.05% Foo $0.01 ; ofxid: 1101.1452687~7.0000486 Expenses:Misc -$0.01 """) self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[1]).format(), """2011/04/05 AUTOMATIC WITHDRAWAL, ELECTRIC BILL WEB(S ) Foo -$34.51 ; ofxid: 1101.1452687~7.0000487 Expenses:Misc $34.51 """) self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[2]).format(), """2011/04/07 RETURNED CHECK FEE, CHECK # 319 FOR $45.33 ON 04/07/11 Foo -$25.00 ; ofxid: 1101.1452687~7.0000488 Expenses:Misc $25.00 """)
def test_checking_custom_payee(self): ofx = OfxParser.parse(open(os.path.join('fixtures', 'checking.ofx'))) converter = OfxConverter( account=ofx.account, name="Foo", payee_format="{memo}") self.assertEqual( converter.format_payee( ofx.account.statement.transactions[0]), 'DIVIDEND EARNED FOR PERIOD OF 03/01/2011 THROUGH 03/31/2011 ANNUAL PERCENTAGE YIELD EARNED IS 0.05%') converter = OfxConverter( account=ofx.account, name="Foo", payee_format="{payee}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'DIVIDEND EARNED FOR PERIOD OF 03') converter = OfxConverter( account=ofx.account, name="Foo", payee_format="{account}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'Foo') converter = OfxConverter( account=ofx.account, name="Foo", payee_format=" {account} ") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'Foo')
def process_ofx(self, cr, uid, data_file, journal_id=False, context=None): """ Import a file in the .OFX format""" if ofxparser is None: raise osv.except_osv(_("Error"), _("OFX parser unavailable because the `ofxparse` Python library cannot be found." "It can be downloaded and installed from `https://pypi.python.org/pypi/ofxparse`.")) try: osuser = getpass.getuser() path = os.path.expanduser('~{}/temp.ofx'.format(osuser)) tempfile = open(path, "w+") tempfile.write(base64.decodestring(data_file)) tempfile.read() # pathname = os.path.dirname('temp.ofx') # path = os.path.join(os.path.abspath(pathname), 'temp.ofx') print "save import statement file {}".format(path) ofx = ofxparser.parse(file(path)) except: raise osv.except_osv(_('Import Error!'), _('Please check OFX file format is proper or not.')) line_ids = [] total_amt = 0.00 try: for transaction in ofx.account.statement.transactions: bank_account_id, partner_id = self._detect_partner(cr, uid, transaction.payee, identifying_field='owner_name', context=context) vals_line = { 'date': transaction.date, 'name': transaction.payee + ': ' + transaction.memo, 'ref': transaction.id, 'amount': transaction.amount, 'partner_id': partner_id, 'bank_account_id': False, } total_amt += float(transaction.amount) line_ids.append((0, 0, vals_line)) except Exception, e: raise osv.except_osv(_('Error!'), _("Following problem has been occurred while importing your file, Please verify the file is proper or not.\n\n %s" % e.message))
def main(): balances = {} for request in config.requests: data = open(request["file"]).read() % request response_data = post(request["url"], data, { "Content-type": "application/x-ofx", "Accept": "*/*, application/x-ofx" }) response = OfxParser.parse(response_data) groups = dict([(request[k], v) for k, v in request.get("groups", {}).items()]) for account in response.accounts: group = groups.get(account.number, "") balances[group] = balances.get(group, 0) + account.statement.balance #balances.append(sum([float(x.statement.balance) for x in response.accounts])) #for label, k in request.get("special", {}).items(): # balance = [a.statement.balance for a in response.accounts if a.number == request[k]] # if balance: # specials.append("%s: %d" % (label, balance[0])) # positive_accounts = set([request[x.strip()] for x in request.get("positive", "").split(", ") if x.strip()]) # positive = sum([float(x.statement.balance) for x in response.accounts if x.number in positive_accounts]) # negative = sum([float(x.statement.balance) for x in response.accounts if x.number not in positive_accounts]) # print positive, negative #print [x.statement for x in response.accounts] #ch, zch, s, c = [a.statement.balance for a in response.accounts] #balance = ch + c #print "%d %d %dk" % (balance, zch, s / 1000) #print "%d %d" % (balance, zch) print "%d %s" % (balances[""] - 10000, ", ".join(["%s: %d" % (k, v) for k, v in balances.items() if k]))
def testThatParseStmtrsReturnsAnAccount(self): stmtrs = soup_maker(self.input) account = OfxParser.parseStmtrs( stmtrs.find('stmtrs'), AccountType.Bank)[0] self.assertEquals('12300 000012345678', account.number) self.assertEquals('160000100', account.routing_number) self.assertEquals('CHECKING', account.account_type)
def testForFourAccounts(self): ofx = OfxParser.parse(open_file('account_listing_aggregation.ofx')) self.assertTrue(hasattr(ofx, 'accounts')) self.assertEquals(len(ofx.accounts), 4) # first account account = ofx.accounts[0] self.assertEquals(account.account_type, 'SAVINGS') self.assertEquals(account.desc, 'USAA SAVINGS') self.assertEquals(account.institution.organization, 'USAA') self.assertEquals(account.number, '0000000001') self.assertEquals(account.routing_number, '314074269') # second account = ofx.accounts[1] self.assertEquals(account.account_type, 'CHECKING') self.assertEquals(account.desc, 'FOUR STAR CHECKING') self.assertEquals(account.institution.organization, 'USAA') self.assertEquals(account.number, '0000000002') self.assertEquals(account.routing_number, '314074269') # third account = ofx.accounts[2] self.assertEquals(account.account_type, 'CREDITLINE') self.assertEquals(account.desc, 'LINE OF CREDIT') self.assertEquals(account.institution.organization, 'USAA') self.assertEquals(account.number, '00000000000003') self.assertEquals(account.routing_number, '314074269') # fourth account = ofx.accounts[3] self.assertEquals(account.account_type, '') self.assertEquals(account.desc, 'MY CREDIT CARD') self.assertEquals(account.institution.organization, 'USAA') self.assertEquals(account.number, '4111111111111111')
def parse(self, file_): # ofxparse workaround # ofxparse doesn't parse the transaction amount correctly. It assumes # that the decimal point separator is always a full-stop; however, it # depends on the locale of the statement. # # This workaround finds the transaction amount in the file and replaces # comma with a full-stop. A temporary file is used to make the change # so the original file data stays intact. with open(file_) as f: data = f.read() data = re.sub(r'<TRNAMT>([-\d]+),([\d]+)', r'<TRNAMT>\1.\2', data) ofx_file = tempfile.NamedTemporaryFile(delete=False) try: with ofx_file as f: f.write(data) # Actual parsing ofx = OfxLibParser.parse(file(ofx_file.name)) i_txs = [] for f_acc in ofx.accounts: for f_tx in f_acc.statement.transactions: p_tx = ParsedTx(f_tx.date.date(), f_tx.amount, unicode(f_acc.number), unicode(f_tx.memo)) i_tx = ImportTx(p_tx) i_txs.append(i_tx) return i_txs finally: os.remove(ofx_file.name)
def import_ofx(self, ofx_file): ofx = OfxParser.parse(file(ofx_file)) idx = {} for s in ofx.security_list: idx[s.uniqueid] = s.ticker c = self.db.cursor() for t in ofx.account.statement.transactions: c.execute("SELECT id FROM stocks WHERE id = ?", [t.id]) row = c.fetchone() if row: print "Skipping duplicate transaction:", t.id continue spydate = t.tradeDate # Fidelity transactions can "close" on a weekend?!? if spydate.weekday() == 5: spydate = spydate - timedelta(days=1) elif spydate.weekday() == 6: spydate = spydate - timedelta(days=2) spy = ystockquote.get_historical_prices('SPY', spydate.strftime("%Y%m%d"), spydate.strftime("%Y%m%d")) spy_price = float(spy[1][4]) spy_units = (float(t.units) * float(t.unit_price)) / spy_price c.execute("INSERT INTO stocks VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", (t.id, idx[t.security], t.security, t.tradeDate, t.settleDate, float(t.units), float(t.unit_price), spy_units, spy_price)) self.db.commit()
def testForFourAccounts(self): ofx = OfxParser.parse(open_file("account_listing_aggregation.ofx")) self.assertTrue(hasattr(ofx, "accounts")) self.assertEquals(len(ofx.accounts), 4) # first account account = ofx.accounts[0] self.assertEquals(account.account_type, "SAVINGS") self.assertEquals(account.desc, "USAA SAVINGS") self.assertEquals(account.institution.organization, "USAA") self.assertEquals(account.number, "0000000001") self.assertEquals(account.routing_number, "314074269") # second account = ofx.accounts[1] self.assertEquals(account.account_type, "CHECKING") self.assertEquals(account.desc, "FOUR STAR CHECKING") self.assertEquals(account.institution.organization, "USAA") self.assertEquals(account.number, "0000000002") self.assertEquals(account.routing_number, "314074269") # third account = ofx.accounts[2] self.assertEquals(account.account_type, "CREDITLINE") self.assertEquals(account.desc, "LINE OF CREDIT") self.assertEquals(account.institution.organization, "USAA") self.assertEquals(account.number, "00000000000003") self.assertEquals(account.routing_number, "314074269") # fourth account = ofx.accounts[3] self.assertEquals(account.account_type, "") self.assertEquals(account.desc, "MY CREDIT CARD") self.assertEquals(account.institution.organization, "USAA") self.assertEquals(account.number, "4111111111111111")
def test_transfer_txn(self): ofx = OfxParser.parse( open( os.path.join( 'fixtures', 'investment_401k.ofx'))) converter = OfxConverter(account=ofx.account, name="Foo", unknownaccount='Expenses:Unknown') if len(ofx.account.statement.transactions) > 2: # older versions of ofxparse would skip these transactions if hasattr(ofx.account.statement.transactions[2], 'tferaction'): # unmerged pull request self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[2]).format(), """2014/06/30 Foo: transfer: out Foo -9.060702 BAZ @ $21.928764 ; ofxid: 1234.12345678.123456-01.3 Transfer $198.69 """) else: self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[2]).format(), """2014/06/30 Foo: transfer Foo -9.060702 BAZ @ $21.928764 ; ofxid: 1234.12345678.123456-01.3 Transfer $198.69 """)
def testForUnclosedTags(self): with open_file('fidelity.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx.account.statement, 'positions')) self.assertEqual(len(ofx.account.statement.positions), 6) self.assertEqual( ofx.account.statement.positions[0].units, Decimal('128.0'))
def post(self): try: bank_statement = self.get_uploads()[0] from ofxparse import OfxParser ofx = OfxParser.parse(bank_statement.open()) user = users.get_current_user() logging.info(ofx.account.number) # The account number logging.info(ofx.account.routing_number) # The transit id (sometimes called branch number) #logging.info(ofx.account.statement # Account information for a period of time logging.info(ofx.account.statement.start_date) # The start date of the transactions logging.info(ofx.account.statement.end_date) # The end date of the transactions #ofx.account.statement.transactions # A list of account activities logging.info(ofx.account.statement.balance) # The money in the account as of the statement for tx in ofx.account.statement.transactions: logging.info(vars(tx)) if tx.type == 'debit': # new expense: new_expense = Expenses(parent=expenses_key(tx.date.month, tx.date.year)) new_expense.user = user new_expense.date = tx.date new_expense.amount = -float(str(tx.amount)) new_expense.category = tx.payee new_expense.exptype = 3 new_expense.put() finally: self.redirect('comptes')
def test_sync_order(self): ledger = Ledger(os.path.join('fixtures', 'empty.lgr')) sync = Synchronizer(ledger) ofx = OfxParser.parse(file(os.path.join('fixtures', 'checking_order.ofx'))) txns = sync.filter(ofx) self.assertTrue(txns[0].date < txns[1].date and txns[1].date < txns[2].date)
def testErrorInTransactionList(self): """There is an error in the transaction list.""" with open_file('error_message.ofx') as f: ofx = OfxParser.parse(f, False) self.assertEqual(ofx.status['code'], 2000) self.assertEqual(ofx.status['severity'], 'ERROR') self.assertEqual(ofx.status['message'], 'General Server Error')
def test_fresh_sync(self): ledger = Ledger(os.path.join('fixtures', 'empty.lgr')) sync = Synchronizer(ledger) ofx = OfxParser.parse(file(os.path.join('fixtures', 'checking.ofx'))) txns1 = ofx.account.statement.transactions txns2 = sync.filter(ofx) self.assertEqual(txns1, txns2)
def parseofx(ofxfile): newaccounts = [] newtransactions = [] skippedtransactions = [] ofx = OfxParser.parse(ofxfile) for account in ofx.accounts: # Check that all account exist, if not create them. if account.account_type == 'SAVINGS': accounttype = 'SAV' elif account.account_type == 'CHECKING': accounttype = 'CUR' else: accounttype = 'CUR' results = Account.objects.filter(account=account.number,sortcode=account.routing_number) if len(results) == 0: # No existing account exists, lets create one accountmodel = Account(account=account.number,sortcode=account.routing_number,accounttype=accounttype) accountmodel.save() newaccounts.append(accountmodel) else: accountmodel = results[0] #Load transactions. for transaction in account.statement.transactions: results = Transaction.objects.filter(account=accountmodel,transid=transaction.id) if len(results) == 0: # No existing transaction exists, lets create one transactionmodel = Transaction(account=accountmodel,memo=transaction.memo,payee=transaction.payee,amount=transaction.amount,transtype=transaction.type,transid=transaction.id) transactionmodel.save() newtransactions.append(transactionmodel) else: transactionmodel = results[0] skippedtransactions.append(transactionmodel) return [newaccounts,newtransactions,skippedtransactions]
def parse_ofx(text): s = StringIO.StringIO(text) ofx = OfxParser.parse(s) patterns = db.session.query(Pattern).filter(Pattern.pattern!='.').all() patterns.append( db.session.query(Pattern).filter(Pattern.pattern=='.').first() ) db_records = set([r.id for r in db.session.query(Record).all()]) records = [ Record(id=t.id, payee=t.payee, date=t.date, amount=t.amount) for t in ofx.account.statement.transactions if t.id not in db_records ] transactions = [] for record in records: matched = False for pattern in patterns: if pattern.match(record): transactions += pattern.transactions(record) matched = True break return transactions
def test_using_ofx_printer(self): with open_file('checking.ofx') as f: ofx = OfxParser.parse(f) fd, name = mkstemp() close(fd) printer = OfxPrinter(ofx=ofx, filename=name) printer.write(tabs=1)
def to_obp_json(account_holder, ofx_file): # if not an ofx file then exist if not ofx_file.endswith('.ofx'): return #Initiate the ofx object ofx = OfxParser.parse(file(ofx_file)) transactions = [] #Get Info about the Holders' Account account_id = ofx.account.number bank = ofx.account.institution.organization kind = ofx.account.account_type #For each transaction transform the OFX transaction to a OBP transaction for transaction in ofx.account.statement.transactions: obp_transaction = {} this_account = { "holder":account_holder, "number":account_id, "kind": kind, "bank":{ "IBAN":"unknown", "national_identifier":"unknown", "name": bank} } other_account = { "holder": transaction.payee or transaction.id, "number":"unknown", "kind": "unknown", "bank":{ "IBAN":"unknown", "national_identifier":"unknown", "name":"unknown"} } details = { "type_en":transaction.type, "type_de":transaction.type, "posted":{"$dt":convert_date(transaction.date)}, "completed":{"$dt":convert_date(transaction.date)}, "new_balance":{ "currency":ofx.account.statement.currency, "amount":str(ofx.account.statement.balance) }, "value":{ "currency":ofx.account.statement.currency, "amount":str(transaction.amount) }, "other_data":transaction.memo } obp_transaction = {'this_account':this_account, 'other_account':other_account, 'details' : details } transactions.append({'obp_transaction':obp_transaction}) #Serializing the object obpjson = json.dumps(transactions) #Return the newly created json return obpjson
def _check_ofx(self, file): if ofxparser is None: return False try: ofx = ofxparser.parse(file) except: return False return ofx
def download_parsed(self, days=60): """Downloaded OFX response parsed by :py:meth:`OfxParser.parse` :param days: Number of days to look back at :type days: integer :rtype: :py:class:`ofxparser.Ofx` """ return OfxParser.parse(self.download(days=days))
def _check_ofx(self, cr, uid, file, context=None): if ofxparser is None: return False try: ofx = ofxparser.parse(file) except: return False return ofx
def testReadAccount(self): with open_file('tiaacref.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx, 'account')) self.assertTrue(hasattr(ofx.account, 'account_id')) self.assertEqual(ofx.account.account_id, '111A1111 22B222 33C333') self.assertTrue(hasattr(ofx.account, 'type')) self.assertEqual(ofx.account.type, AccountType.Investment)
def testDateFieldMissing(self): ''' The test file contains three transactions in a single statement. They fail due to: 1) No date 2) Empty date 3) Invalid date ''' ofx = OfxParser.parse(open_file('fail_nice/date_missing.ofx'), False) self.assertEquals(len(ofx.account.statement.transactions), 0) self.assertEquals(len(ofx.account.statement.discarded_entries), 3) self.assertEquals(len(ofx.account.statement.warnings), 0) # Test that it raises an error otherwise. self.assertRaises(OfxParserException, OfxParser.parse, open_file('fail_nice/date_missing.ofx'))
def testForUnclosedTags(self): with open_file('vanguard.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx, 'account')) self.assertTrue(hasattr(ofx.account, 'statement')) self.assertTrue(hasattr(ofx.account.statement, 'transactions')) self.assertEqual(len(ofx.account.statement.transactions), 1) self.assertEqual(ofx.account.statement.transactions[0].id, '01234567890.0123.07152011.0') self.assertEqual(ofx.account.statement.transactions[0] .tradeDate, datetime(2011, 7, 15, 21)) self.assertEqual(ofx.account.statement.transactions[0] .settleDate, datetime(2011, 7, 15, 21)) self.assertTrue(hasattr(ofx.account.statement, 'positions')) self.assertEqual(len(ofx.account.statement.positions), 2) self.assertEqual( ofx.account.statement.positions[0].units, Decimal('102.0'))
def testReadPositions(self): with open_file('tiaacref.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx, 'account')) self.assertTrue(hasattr(ofx.account, 'statement')) self.assertTrue(hasattr(ofx.account.statement, 'positions')) expected_positions = [{ 'security': '222222126', 'units': Decimal('13.0763'), 'unit_price': Decimal('1.0000'), 'market_value': Decimal('13.0763') }, { 'security': '222222217', 'units': Decimal('1.0000'), 'unit_price': Decimal('25.5785'), 'market_value': Decimal('25.5785') }, { 'security': '222222233', 'units': Decimal('8.7605'), 'unit_price': Decimal('12.4823'), 'market_value': Decimal('109.3512') }, { 'security': '222222258', 'units': Decimal('339.2012'), 'unit_price': Decimal('12.3456'), 'market_value': Decimal('4187.6423') }, { 'security': '111111111', 'units': Decimal('543.71'), 'unit_price': Decimal('1'), 'market_value': Decimal('543.71') }, { 'security': '333333200', 'units': Decimal('2.00'), 'unit_price': Decimal('10.00'), 'market_value': Decimal('20.00') }] self.assertEqual(len(ofx.account.statement.positions), len(expected_positions)) for pos, expected_pos in zip(ofx.account.statement.positions, expected_positions): self.assertEqual(pos.security, expected_pos['security']) self.assertEqual(pos.units, expected_pos['units']) self.assertEqual(pos.unit_price, expected_pos['unit_price']) self.assertEqual(pos.market_value, expected_pos['market_value'])
def test_parses_time_offset(self): ''' Test that we handle GMT offset ''' self.assertEquals( OfxParser.parseOfxDateTime('20001201120000 [0:GMT]'), datetime(2000, 12, 1, 12, 0) ) self.assertEquals( OfxParser.parseOfxDateTime('19991201120000 [1:ITT]'), datetime(1999, 12, 1, 11, 0) ) self.assertEquals( OfxParser.parseOfxDateTime('19881201230100 [-5:EST]'), datetime(1988, 12, 2, 4, 1) ) self.assertEquals( OfxParser.parseOfxDateTime('20120229230100 [-6:CAT]'), datetime(2012, 3, 1, 5, 1) ) self.assertEquals( OfxParser.parseOfxDateTime('20120412120000 [-5.5:XXX]'), datetime(2012, 04, 12, 17, 30)) self.assertEquals( OfxParser.parseOfxDateTime('20120412120000 [-5:XXX]'), datetime(2012, 04, 12, 17)) self.assertEquals( OfxParser.parseOfxDateTime('20120922230000 [+9:JST]'), datetime(2012, 9, 22, 14, 0) )
def main(): with open('wealthfront.qfx', 'rb') as qfxfile: ofx = OfxParser.parse(qfxfile) rows = [] for transaction in ofx.account.statement.transactions: row = {} public_props = (name for name in dir(transaction) if not name.startswith('_')) for field in transaction_fields: if field in dir(transaction): row[field] = getattr(transaction, field) if 'security' in dir(transaction): security = find_security(ofx.security_list, transaction.security) row['ticker'] = security.ticker row['name'] = security.name if 'date' not in row: row['date'] = transaction.tradeDate if row['type'] == 'buymf': row['type'] = 'buy' row['security_type'] = 'fund' if row['type'] == 'sellmf': row['type'] = 'sell' row['security_type'] = 'fund' if row['type'] == 'buystock': row['type'] = 'buy' row['security_type'] = 'stock' if row['type'] == 'sellstock': row['type'] = 'sell' row['security_type'] = 'stock' rows.append(row) rows.sort(key=lambda row: row['date']) with open('result.csv', 'w', newline='') as csvfile: writer = csv.DictWriter(csvfile, delimiter=',', quotechar='"', quoting=csv.QUOTE_MINIMAL, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows)
def testEverything(self): ofx = OfxParser.parse(open_file('bank_medium.ofx')) self.assertEquals('12300 000012345678', ofx.account.number) self.assertEquals('160000100', ofx.account.routing_number) self.assertEquals('CHECKING', ofx.account.account_type) self.assertEquals(Decimal('382.34'), ofx.account.statement.balance) # Todo: support values in decimal or int form. #self.assertEquals('15', ofx.bank_account.statement.balance_in_pennies) self.assertEquals(Decimal('682.34'), ofx.account.statement.available_balance) self.assertEquals(datetime(2009, 4, 1), ofx.account.statement.start_date) self.assertEquals(datetime(2009, 5, 23, 12, 20, 17), ofx.account.statement.end_date) self.assertEquals(3, len(ofx.account.statement.transactions)) transaction = ofx.account.statement.transactions[0] self.assertEquals("MCDONALD'S #112", transaction.payee) self.assertEquals('pos', transaction.type) self.assertEquals(Decimal('-6.60'), transaction.amount)
def read(self, fso): result = [] with fso.open(self.work_file.get_file_name(), 'r') as ofx_file: ofx = OfxParser.parse(ofx_file) for current in ofx.account.statement.transactions: # the dictionary object we are populating record = {} record['transaction_date'] = current.date record['name'] = current.payee record['amount'] = str(abs(current.amount)) # accumulate results result.append(record) return result
def test_checking_custom_payee(self): ofx = OfxParser.parse(file(os.path.join('fixtures', 'checking.ofx'))) converter = OfxConverter(ofx=ofx, name="Foo", payee_format="{memo}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'DIVIDEND EARNED FOR PERIOD OF 03/01/2011 THROUGH 03/31/2011 ANNUAL PERCENTAGE YIELD EARNED IS 0.05%') converter = OfxConverter(ofx=ofx, name="Foo", payee_format="{payee}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'DIVIDEND EARNED FOR PERIOD OF 03') converter = OfxConverter(ofx=ofx, name="Foo", payee_format="{account}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'Foo') converter = OfxConverter(ofx=ofx, name="Foo", payee_format=" {account} ") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[0]), 'Foo')
def test_investments(self): ofx = OfxParser.parse(file(os.path.join('fixtures', 'fidelity.ofx'))) converter = OfxConverter(ofx=ofx, name="Foo") self.assertEqualLedgerPosting( converter.convert(ofx.account.statement.transactions[0]).format(), """2012/07/20 YOU BOUGHT Foo 100.00000 INTC @ $25.635000000 ; ofxid: 7776.01234567890.0123456789020201120120720 Assets:Unknown -$2563.50 """) # test no payee/memo self.assertEqualLedgerPosting( converter.convert(ofx.account.statement.transactions[1]).format(), """2012/07/27 Foo: buystock Foo 128.00000 SDRL @ $39.390900000 ; ofxid: 7776.01234567890.0123456789020901120120727 Assets:Unknown -$5042.04 """)
def test_dynamic_account(self): ofx = OfxParser.parse(open(os.path.join('fixtures', 'checking.ofx'))) ledger = Ledger( os.path.join( 'fixtures', 'checking-dynamic-account.lgr')) converter = OfxConverter( account=ofx.account, name="Assets:Foo", ledger=ledger) self.assertEqualLedgerPosting( converter.convert( ofx.account.statement.transactions[1]).format(), """2011/04/05 AUTOMATIC WITHDRAWAL, ELECTRIC BILL WEB(S ) Assets:Foo -$34.51 ; ofxid: 1101.1452687~7.0000487 Expenses:Bar $34.51 """)
def process_ofx(self, cr, uid, data_file, journal_id=False, context=None): """ Import a file in the .OFX format""" if ofxparser is None: raise osv.except_osv( _("Error"), _("OFX parser unavailable because the `ofxparse` Python library cannot be found." "It can be downloaded and installed from `https://pypi.python.org/pypi/ofxparse`." )) try: tempfile = open("temp.ofx", "w+") tempfile.write(base64.decodestring(data_file)) tempfile.read() pathname = os.path.dirname('temp.ofx') path = os.path.join(os.path.abspath(pathname), 'temp.ofx') ofx = ofxparser.parse(file(path)) except: raise osv.except_osv( _('Import Error!'), _('Please check OFX file format is proper or not.')) line_ids = [] total_amt = 0.00 try: for transaction in ofx.account.statement.transactions: bank_account_id, partner_id = self._detect_partner( cr, uid, transaction.payee, identifying_field='owner_name', context=context) vals_line = { 'date': transaction.date, 'name': transaction.payee + ': ' + transaction.memo, 'ref': transaction.id, 'amount': transaction.amount, 'partner_id': partner_id, 'bank_account_id': bank_account_id, } total_amt += float(transaction.amount) line_ids.append((0, 0, vals_line)) except Exception, e: raise osv.except_osv( _('Error!'), _("Following problem has been occurred while importing your file, Please verify the file is proper or not.\n\n %s" % e.message))
def parseofx(ofxfile): newaccounts = [] newtransactions = [] skippedtransactions = [] ofx = OfxParser.parse(ofxfile) for account in ofx.accounts: # Check that all account exist, if not create them. if account.account_type == 'SAVINGS': accounttype = 'SAV' elif account.account_type == 'CHECKING': accounttype = 'CUR' else: accounttype = 'CUR' results = Account.objects.filter(account=account.number, sortcode=account.routing_number) if len(results) == 0: # No existing account exists, lets create one accountmodel = Account(account=account.number, sortcode=account.routing_number, accounttype=accounttype) accountmodel.save() newaccounts.append(accountmodel) else: accountmodel = results[0] #Load transactions. for transaction in account.statement.transactions: results = Transaction.objects.filter(account=accountmodel, transid=transaction.id) if len(results) == 0: # No existing transaction exists, lets create one transactionmodel = Transaction(account=accountmodel, memo=transaction.memo, payee=transaction.payee, amount=transaction.amount, transtype=transaction.type, transid=transaction.id) transactionmodel.save() newtransactions.append(transactionmodel) else: transactionmodel = results[0] skippedtransactions.append(transactionmodel) return [newaccounts, newtransactions, skippedtransactions]
def testPositions(self): with open_file('td_ameritrade.ofx') as f: ofx = OfxParser.parse(f) account = ofx.accounts[0] statement = account.statement positions = statement.positions self.assertEquals(len(positions), 2) expected_positions = [ { 'security': '023135106', 'units': Decimal('1'), 'unit_price': Decimal('1000'), 'market_value': Decimal('1000') }, { 'security': '912810RW0', 'units': Decimal('1000'), 'unit_price': Decimal('100'), 'market_value': Decimal('1000') } ] for pos, expected_pos in zip(positions, expected_positions): self.assertEqual(pos.security, expected_pos['security']) self.assertEqual(pos.units, expected_pos['units']) self.assertEqual(pos.unit_price, expected_pos['unit_price']) self.assertEqual(pos.market_value, expected_pos['market_value']) expected_securities = [ { 'uniqueid': '023135106', 'ticker': 'AMZN', 'name': 'Amazon.com, Inc. - Common Stock' }, { 'uniqueid': '912810RW0', 'ticker': '912810RW0', 'name': 'US Treasury 2047' } ] for sec, expected_sec in zip(ofx.security_list, expected_securities): self.assertEqual(sec.uniqueid, expected_sec['uniqueid']) self.assertEqual(sec.ticker, expected_sec['ticker']) self.assertEqual(sec.name, expected_sec['name'])
def _parse_ofx(self, data_file): data_file = data_file.replace('\r\n', '\n').replace('\r', '\n') ofx = OfxParser.parse(StringIO.StringIO(data_file)) transacoes = [] total = 0.0 for transacao in ofx.account.statement.transactions: transacoes.append({ 'date': transacao.date, 'name': transacao.payee + (transacao.memo and ': ' + transacao.memo or ''), 'ref': transacao.id, 'amount': transacao.amount, 'unique_import_id': transacao.id }) total += float(transacao.amount) journal = self.journal_id if not self.force_journal_account: dummy, journal = self._find_additional_data( ofx.account.statement.currency, ofx.account.number) name = u"%s - %s até %s" % ( journal.name, ofx.account.statement.start_date.strftime('%d/%m/%Y'), ofx.account.statement.end_date.strftime('%d/%m/%Y')) vals_bank_statement = { 'name': name, 'transactions': transacoes, 'balance_start': ofx.account.statement.balance, 'balance_end_real': float(ofx.account.statement.balance) + total, } account_number = ofx.account.number if self.force_journal_account: account_number = self.journal_id.bank_acc_number return (ofx.account.statement.currency, account_number, [vals_bank_statement])
def test_investments_custom_payee(self): ofx = OfxParser.parse( open( os.path.join( 'fixtures', 'investment_401k.ofx'))) converter = OfxConverter( account=ofx.account, name="Foo", payee_format="{txntype}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[1]), 'transfer') converter = OfxConverter( account=ofx.account, name="Foo", payee_format="{tferaction}") self.assertEqual( converter.format_payee(ofx.account.statement.transactions[1]), 'in')
def testThatParseTransactionReturnsATransaction(self): input = ''' <STMTTRN> <TRNTYPE>POS <DTPOSTED>20090401122017.000[-5:EST] <TRNAMT>-6.60 <FITID>0000123456782009040100001 <NAME>MCDONALD'S #112 <MEMO>POS MERCHANDISE;MCDONALD'S #112 </STMTTRN> ''' txn = soup_maker(input) transaction = OfxParser.parseTransaction(txn.find('stmttrn')) self.assertEqual('pos', transaction.type) self.assertEqual(datetime( 2009, 4, 1, 12, 20, 17) - timedelta(hours=-5), transaction.date) self.assertEqual(Decimal('-6.60'), transaction.amount) self.assertEqual('0000123456782009040100001', transaction.id) self.assertEqual("MCDONALD'S #112", transaction.payee) self.assertEqual("POS MERCHANDISE;MCDONALD'S #112", transaction.memo)
def testTransferAggregate(self): ofx = OfxParser.parse(open_file('investment_401k.ofx')) expected_txns = [{'id': '1', 'type': 'buymf', 'units': Decimal('8.846699'), 'unit_price': Decimal('22.2908'), 'total': Decimal('-197.2'), 'security': 'FOO'}, {'id': '2', 'type': 'transfer', 'units': Decimal('6.800992'), 'unit_price': Decimal('29.214856'), 'total': Decimal('0.0'), 'security': 'BAR'}, {'id': '3', 'type': 'transfer', 'units': Decimal('-9.060702'), 'unit_price': Decimal('21.928764'), 'total': Decimal('0.0'), 'security': 'BAZ'}] for txn, expected_txn in zip(ofx.account.statement.transactions, expected_txns): self.assertEquals(txn.id, expected_txn['id']) self.assertEquals(txn.type, expected_txn['type']) self.assertEquals(txn.units, expected_txn['units']) self.assertEquals(txn.unit_price, expected_txn['unit_price']) self.assertEquals(txn.total, expected_txn['total']) self.assertEquals(txn.security, expected_txn['security']) expected_positions = [{'security': 'FOO', 'units': Decimal('17.604312'), 'unit_price': Decimal('22.517211')}, {'security': 'BAR', 'units': Decimal('13.550983'), 'unit_price': Decimal('29.214855')}, {'security': 'BAZ', 'units': Decimal('0.0'), 'unit_price': Decimal('0.0')}] for pos, expected_pos in zip(ofx.account.statement.positions, expected_positions): self.assertEquals(pos.security, expected_pos['security']) self.assertEquals(pos.units, expected_pos['units']) self.assertEquals(pos.unit_price, expected_pos['unit_price'])
def _ofx_to_db(self, account_name, fname, ofxdata): """ Put OFX Data to the DB :param account_name: account name to download :type account_name: str :param ofxdata: raw OFX data :type ofxdata: str :param fname: filename OFX was written to :type fname: str """ logger.debug('Parsing OFX') ofx = OfxParser.parse(StringIO(ofxdata)) logger.debug('Updating OFX in DB') _, count_new, count_upd = self._client.update_statement_ofx( self._account_data[account_name]['id'], ofx, filename=fname) logger.info( 'Account "%s" - inserted %d new OFXTransaction(s), updated ' '%d existing OFXTransaction(s)', account_name, count_new, count_upd) logger.debug('Done updating OFX in DB')
def test_transfer_txn(self): ofx = OfxParser.parse(file(os.path.join('fixtures', 'investment_401k.ofx'))) converter = OfxConverter(ofx=ofx, name="Foo", unknownaccount='Expenses:Unknown') if len(ofx.account.statement.transactions) > 2: # older versions of ofxparse would skip these transactions if hasattr(ofx.account.statement.transactions[2], 'tferaction'): # unmerged pull request self.assertEqualLedgerPosting(converter.convert(ofx.account.statement.transactions[2]).format(), """2014/06/30 Foo: transfer: out Foo -9.060702 BAZ @ $21.928764 ; ofxid: 1234.12345678.123456-01.3 Transfer $198.69 """) else: self.assertEqualLedgerPosting(converter.convert(ofx.account.statement.transactions[2]).format(), """2014/06/30 Foo: transfer Foo -9.060702 BAZ @ $21.928764 ; ofxid: 1234.12345678.123456-01.3 Transfer $198.69 """)
def _do_one_file(self, acct_id, path): """ Parse one OFX file and use OFXUpdater to upsert it into the DB. :param acct_id: Account ID number :type acct_id: int :param path: absolute path to OFX/QFX file :type path: str """ logger.debug('Handle file %s for Account %d', path, acct_id) with open(path, 'rb') as fh: ofx_str = fh.read() ofx = OfxParser.parse(BytesIO(ofx_str)) logger.debug('Parsed OFX') fname = os.path.basename(path) mtime = datetime.fromtimestamp(os.path.getmtime(path), tz=UTC) self._client.update_statement_ofx(acct_id, ofx, mtime=mtime, filename=fname) logger.debug('Done updating')
def import_transactions(ofx_path): transactions = [] with open(ofx_path) as ofx_file: logger.info('Opening ofx file %s', ofx_path) try: ofx = OfxParser.parse(ofx_file) for transaction in ofx.account.statement.transactions: try: transaction_time = transaction.date transactions.append(ImportStatement( notes=transaction.payee, book_date=transaction_time.date(), transaction_date=transaction_time.date(), amount=transaction.amount )) except ValueError: logger.error('Cannot import transaction: {}'.format(transaction)) pass except ValueError: logger.error('Failed to import all transactions! Wrong file format?') return transactions
def test_checking(self): ofx = OfxParser.parse(file(os.path.join('fixtures', 'checking.ofx'))) converter = OfxConverter(ofx=ofx, name="Foo") self.assertEqualLedgerPosting(converter.convert(ofx.account.statement.transactions[0]).format(), """2011/03/31 DIVIDEND EARNED FOR PERIOD OF 03/01/2011 THROUGH 03/31/2011 ANNUAL PERCENTAGE YIELD EARNED IS 0.05% Foo $0.01 ; ofxid: 1101.1452687~7.0000486 Expenses:Misc -$0.01 """) self.assertEqualLedgerPosting(converter.convert(ofx.account.statement.transactions[1]).format(), """2011/04/05 AUTOMATIC WITHDRAWAL, ELECTRIC BILL WEB(S ) Foo -$34.51 ; ofxid: 1101.1452687~7.0000487 Expenses:Misc $34.51 """) self.assertEqualLedgerPosting(converter.convert(ofx.account.statement.transactions[2]).format(), """2011/04/07 RETURNED CHECK FEE, CHECK # 319 FOR $45.33 ON 04/07/11 Foo -$25.00 ; ofxid: 1101.1452687~7.0000488 Expenses:Misc $25.00 """)
def process_file(self, file_object): """Read the QFX file & Return the standardized data.""" ofx_data = OfxParser.parse(file_object) data = [] space_reducing_regex = re.compile(r'\s\s+') for transaction in ofx_data.account.statement.transactions: item = { 'date': transaction.date, 'check_number': transaction.checknum, 'memo': space_reducing_regex.sub( ' ', self.clean_memo(transaction.memo)), 'amount': transaction.amount, } if transaction.type in ('debit', 'check'): item['type'] = 'withdrawal' if item['check_number'] == '': item['check_number'] = '0' else: item['type'] = 'deposit' data.append(item) return data
def __init__(self, ofx_path): with codecs.open(ofx_path) as fileobj: self.ofx = OfxParser.parse(fileobj) self.shares = [] for t in self.ofx.account.statement.transactions: exchange, symbol = t.security.split(':') if exchange == 'TLV': continue share = self.get_share(symbol, exchange) if not share: share = Share(symbol, exchange) self.shares.append(share) share.add_transaction( str(t.tradeDate), float(t.units), float(t.unit_price), t.type, int(t.commission), )
def read_ofx(): global full_list names = filedialog.askopenfilenames() for name in names: print(name) with open(name, 'r') as ofx_file: ofx = OfxParser.parse(ofx_file) accounts = ofx.accounts for a in accounts: print(a.account_id, a.institution, a.account_type) transactions = a.statement.transactions for t in transactions: entry = { "Account": a.number, "Date": t.date, "Amount": t.amount } k = t.memo + " " + t.payee # replace multiple spaces with single k = re.sub(r'\s\s+', " ", k) entry["Description"] = k if entry["Description"] not in cats_dict: cats_dict[entry["Description"]] = "" entry["Category"] = cats_dict[entry["Description"]] full_list.append(entry) # sort the list in ascending date order full_list = sorted(full_list, key=lambda dum: dum["Date"])
def test_investments(self): with open(os.path.join('fixtures', 'fidelity.ofx'), 'rb') as ofx_file: ofx = OfxParser.parse(ofx_file) converter = OfxConverter(account=ofx.account, name="Foo", security_list=SecurityList(ofx)) self.assertEqualLedgerPosting( converter.convert(ofx.account.statement.transactions[0]).format(), """2012/07/20 YOU BOUGHT Foo 100.00000 INTC @ $25.635000000 ; ofxid: 7776.01234567890.0123456789020201120120720 Assets:Unknown -$2563.50 Expenses:Commission $7.95 """) # test no payee/memo self.assertEqualLedgerPosting( converter.convert(ofx.account.statement.transactions[1]).format(), """2012/07/27 Foo: buystock Foo 128.00000 SDRL @ $39.390900000 ; ofxid: 7776.01234567890.0123456789020901120120727 Assets:Unknown -$5042.04 Expenses:Commission $7.95 """)
def testForOptBuySell(self): import glob one_buy = False one_sell = False for f_name in glob.glob("C:/Users/Eric/SkyDrive/OFX/*.OFX"): ofx = OfxParser.parse(open(f_name)) self.assertTrue(hasattr(ofx, 'account')) self.assertTrue(hasattr(ofx.account, 'statement')) self.assertTrue(hasattr(ofx.account.statement, 'transactions')) for t in ofx.account.statement.transactions: self.assertNotEqual(t.type, t.Unknown, "No transactions should be Unknown") if t.type == t.BuyOption: one_buy = True self.assertIn(t.sub_type, ['BUYTOOPEN', 'BUYTOCLOSE']) elif t.type == t.SellOption: one_sell = True self.assertIn(t.sub_type, ['SELLTOOPEN', 'SELLTOCLOSE']) self.assertTrue(one_sell, "At least one option sale should be found") self.assertTrue(one_buy, "At least one option buy should be found")
def testThatParseTransactionWithNullAmountIgnored(self): """A null transaction value is converted to 0. Some banks use a null transaction to include interest rate changes on statements. """ input_template = ''' <STMTTRN> <TRNTYPE>DEP <DTPOSTED>20130306 <TRNAMT>{amount} <FITID>2013030601009100 <CHECKNUM>700 <MEMO>DEPOSITO ONLINE </STMTTRN> ''' for amount in ("null", "-null"): input = input_template.format(amount=amount) txn = soup_maker(input) transaction = OfxParser.parseTransaction(txn.find('stmttrn')) self.assertEqual(0, transaction.amount)
def testForFourAccounts(self): with open_file('account_listing_aggregation.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx, 'accounts')) self.assertEqual(len(ofx.accounts), 4) # first account account = ofx.accounts[0] self.assertEqual(account.account_type, 'SAVINGS') self.assertEqual(account.desc, 'USAA SAVINGS') self.assertEqual(account.institution.organization, 'USAA') self.assertEqual(account.number, '0000000001') self.assertEqual(account.routing_number, '314074269') # second account = ofx.accounts[1] self.assertEqual(account.account_type, 'CHECKING') self.assertEqual(account.desc, 'FOUR STAR CHECKING') self.assertEqual(account.institution.organization, 'USAA') self.assertEqual(account.number, '0000000002') self.assertEqual(account.routing_number, '314074269') # third account = ofx.accounts[2] self.assertEqual(account.account_type, 'CREDITLINE') self.assertEqual(account.desc, 'LINE OF CREDIT') self.assertEqual(account.institution.organization, 'USAA') self.assertEqual(account.number, '00000000000003') self.assertEqual(account.routing_number, '314074269') # fourth account = ofx.accounts[3] self.assertEqual(account.account_type, '') self.assertEqual(account.desc, 'MY CREDIT CARD') self.assertEqual(account.institution.organization, 'USAA') self.assertEqual(account.number, '4111111111111111')
def testSTMTTRNInInvestmentBank(self): with open_file('fidelity-savings.ofx') as f: ofx = OfxParser.parse(f) self.assertTrue(hasattr(ofx.account.statement, 'transactions')) self.assertEqual(len(ofx.account.statement.transactions), 4) tx = ofx.account.statement.transactions[0] self.assertEqual('check', tx.type) self.assertEqual( datetime(2012, 7, 20, 0, 0, 0) - timedelta(hours=-4), tx.date) self.assertEqual(Decimal('-1500.00'), tx.amount) self.assertEqual('X0000000000000000000001', tx.id) self.assertEqual('Check Paid #0000001001', tx.payee) self.assertEqual('Check Paid #0000001001', tx.memo) tx = ofx.account.statement.transactions[1] self.assertEqual('dep', tx.type) self.assertEqual( datetime(2012, 7, 27, 0, 0, 0) - timedelta(hours=-4), tx.date) self.assertEqual(Decimal('115.8331'), tx.amount) self.assertEqual('X0000000000000000000002', tx.id) self.assertEqual('TRANSFERRED FROM VS X10-08144', tx.payee) self.assertEqual('TRANSFERRED FROM VS X10-08144-1', tx.memo)