Compare commits
No commits in common. "9783960857240928d909d086a931a7d553930736" and "140f8fe6c7aa7313989df9c0f1c118b7e97b0a6f" have entirely different histories.
9783960857
...
140f8fe6c7
40 changed files with 334 additions and 5549 deletions
|
@ -1,4 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import vanth.celery
|
|
||||||
|
|
||||||
vanth.celery.run()
|
|
|
@ -1,23 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import argparse
|
|
||||||
|
|
||||||
import vanth.main
|
|
||||||
import vanth.ofxhome
|
|
||||||
import vanth.platform.ofxsource
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
vanth.main.setup_logging()
|
|
||||||
config = vanth.main.get_config()
|
|
||||||
vanth.main.create_db_connection(config)
|
|
||||||
|
|
||||||
parser = argparse.ArgumentParser()
|
|
||||||
parser.add_argument('dbfile', help='The database file of XML dumped from the open OFX Home DB')
|
|
||||||
args = parser.parse_args()
|
|
||||||
|
|
||||||
with open(args.dbfile, 'r') as f:
|
|
||||||
data = vanth.ofxhome.parse(f.read())
|
|
||||||
vanth.platform.ofxsource.ensure_exist(data)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,8 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import vanth.celery
|
|
||||||
|
|
||||||
def main():
|
|
||||||
vanth.celery.my_print.delay()
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
|
@ -1,7 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import requests
|
|
||||||
import sys
|
|
||||||
|
|
||||||
payload = {'username': 'eliribble', 'password': sys.argv[1]}
|
|
||||||
response = requests.post('http://www.vanth.com/login/', data=payload)
|
|
||||||
print(response.status_code, response.text)
|
|
|
@ -1,11 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import vanth.main
|
|
||||||
import vanth.download
|
|
||||||
|
|
||||||
def main():
|
|
||||||
config = vanth.main.get_config()
|
|
||||||
vanth.main.create_db_connection(config)
|
|
||||||
print(vanth.download.do_all())
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
6
bootstrap-theme.min.css
vendored
6
bootstrap-theme.min.css
vendored
File diff suppressed because one or more lines are too long
6
bootstrap.min.css
vendored
6
bootstrap.min.css
vendored
File diff suppressed because one or more lines are too long
7
bootstrap.min.js
vendored
7
bootstrap.min.js
vendored
File diff suppressed because one or more lines are too long
|
@ -1,7 +0,0 @@
|
||||||
BROKER_URL = 'amqp://guest:guest@localhost:5672//'
|
|
||||||
|
|
||||||
CELERY_IMPORTS = ('vanth.celery_worker', 'vanth.celery')
|
|
||||||
CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
|
|
||||||
CELERY_TASK_SERIALIZER = 'json'
|
|
||||||
|
|
||||||
CELERY_ALWAYS_EAGER = True
|
|
6
jquery.min.js
vendored
6
jquery.min.js
vendored
File diff suppressed because one or more lines are too long
82
ofx.py
82
ofx.py
|
@ -1,82 +0,0 @@
|
||||||
import datetime
|
|
||||||
import pprint
|
|
||||||
import logging
|
|
||||||
import ofxtools
|
|
||||||
import io
|
|
||||||
import requests
|
|
||||||
import sys
|
|
||||||
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
def parse(content):
|
|
||||||
tree = ofxtools.Parser.OFXTree()
|
|
||||||
tree.parse(io.StringIO(content))
|
|
||||||
return tree
|
|
||||||
|
|
||||||
def get_transactions(tree):
|
|
||||||
return [parse_transaction(transaction) for transaction in tree.findall('.//STMTTRN')]
|
|
||||||
|
|
||||||
def _parse_datetime(timestamp):
|
|
||||||
return datetime.datetime.strptime(timestamp, '%Y%m%d%H%M%S.000')
|
|
||||||
|
|
||||||
def parse_transaction(transaction):
|
|
||||||
return {
|
|
||||||
'available' : _parse_datetime(transaction.find('./DTAVAIL').text),
|
|
||||||
'amount' : float(transaction.find('./TRNAMT').text),
|
|
||||||
'id' : transaction.find('./FITID').text,
|
|
||||||
'memo' : transaction.find('./MEMO').text,
|
|
||||||
'name' : transaction.find('./NAME').text,
|
|
||||||
'posted' : _parse_datetime(transaction.find('./DTPOSTED').text),
|
|
||||||
'type' : transaction.find('./TRNTYPE').text,
|
|
||||||
}
|
|
||||||
|
|
||||||
def main():
|
|
||||||
logging.basicConfig()
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
with open(sys.argv[1], 'r') as f:
|
|
||||||
content = f.read()
|
|
||||||
tree = parse(content)
|
|
||||||
transactions = get_transactions(tree)
|
|
||||||
for transaction in transactions[:10]:
|
|
||||||
if transaction['amount'] > 0:
|
|
||||||
continue
|
|
||||||
pprint.pprint(transaction)
|
|
||||||
payload = {
|
|
||||||
'amount' : transaction['amount'],
|
|
||||||
'currency' : {
|
|
||||||
'code' : 'USD',
|
|
||||||
'rate' : 1.0,
|
|
||||||
'fixed' : False,
|
|
||||||
},
|
|
||||||
'date' : transaction['posted'].date().isoformat(),
|
|
||||||
'desc' : transaction['name'],
|
|
||||||
'account' : '2553488',
|
|
||||||
'category' : '50448856',
|
|
||||||
'tags' : [],
|
|
||||||
'extra' : {
|
|
||||||
'available' : transaction['available'].date().isoformat(),
|
|
||||||
'fid' : transaction['id'],
|
|
||||||
'memo' : transaction['memo'],
|
|
||||||
'type' : transaction['type'],
|
|
||||||
},
|
|
||||||
}
|
|
||||||
continue
|
|
||||||
response = requests.post('https://api.toshl.com/entries', json=payload, auth=('4cc0e2c6-7b91-4198-9759-df7f873c4d0d4f44474d-e0e8-4e40-b755-7dfb71955cb2', ''))
|
|
||||||
if not response.ok:
|
|
||||||
print(response.status_code)
|
|
||||||
print(response.text)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
print("uploaded")
|
|
||||||
pprint.pprint(transactions[:10])
|
|
||||||
return
|
|
||||||
root = tree.getroot()
|
|
||||||
pprint(root)
|
|
||||||
|
|
||||||
def print_tree(root, indent=0):
|
|
||||||
print("{}{}: {}".format('\t'*indent, root.tag, root.text if root.text else ''))
|
|
||||||
for child in root.getchildren():
|
|
||||||
pprint(child, indent=indent+1)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
1
setup.py
1
setup.py
|
@ -101,7 +101,6 @@ def main():
|
||||||
'chryso==1.7',
|
'chryso==1.7',
|
||||||
'Flask==0.10.1',
|
'Flask==0.10.1',
|
||||||
'flask-login==0.3.2',
|
'flask-login==0.3.2',
|
||||||
'ofxparse==0.15',
|
|
||||||
'sepiida==5.27',
|
'sepiida==5.27',
|
||||||
],
|
],
|
||||||
extras_require = {
|
extras_require = {
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
{% extends 'layout.html' %}
|
{% extends 'layout.html' %}
|
||||||
{% block main_content %}
|
{% block main_content %}
|
||||||
<h1>{{ account.name }}</h1>
|
<h1>{{ account.name }}</h1>
|
||||||
{% include 'update_button.html' %}
|
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<tr><td>Current balance</td><td>{{ account.balance }}</td></tr>
|
<tr><td>Current balance</td><td>{{ account.balance }}</td></tr>
|
||||||
<tr><td>Last updated</td><td>{{ account.last_updated }}</td></tr>
|
<tr><td>Last updated</td><td>{{ account.last_updated }}</td></tr>
|
||||||
|
@ -16,7 +15,7 @@
|
||||||
<td>{{ record.name }}</a></td>
|
<td>{{ record.name }}</a></td>
|
||||||
<td>{{ record.type }}</td>
|
<td>{{ record.type }}</td>
|
||||||
<td>{{ record.amount }}</td>
|
<td>{{ record.amount }}</td>
|
||||||
<td>{{ record.posted }}</td>
|
<td>{{ account.posted }}</td>
|
||||||
<td>
|
<td>
|
||||||
<form method="POST" action="/update/">
|
<form method="POST" action="/update/">
|
||||||
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
|
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
|
||||||
|
@ -29,10 +28,4 @@
|
||||||
{% else %}
|
{% else %}
|
||||||
<p>This account does not have any transactions yet</p>
|
<p>This account does not have any transactions yet</p>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<form method="POST" action="/transactions/" enctype="multipart/form-data">
|
|
||||||
<p>Want to upload your own transactions? Cool. Do it here.</p>
|
|
||||||
<input type="hidden" name="account_uuid" value="{{ account.uuid }}">
|
|
||||||
<input type="file" name="transactions" id="transactions">
|
|
||||||
<input type="submit">
|
|
||||||
</form>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
@ -11,7 +11,10 @@
|
||||||
<td>{{ account.source.name }}</td>
|
<td>{{ account.source.name }}</td>
|
||||||
<td>{{ account.last_updated }}</td>
|
<td>{{ account.last_updated }}</td>
|
||||||
<td>
|
<td>
|
||||||
{% include 'update_button.html' %}
|
<form method="POST" action="/update/">
|
||||||
|
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
|
||||||
|
<input type="submit" value="Update" class="btn btn-primary"></input>
|
||||||
|
</form>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
|
|
|
@ -1,5 +0,0 @@
|
||||||
{% extends 'base.html' %}
|
|
||||||
{% block body %}
|
|
||||||
<h1>You hit an error</h1>
|
|
||||||
<p>{{ error }}</p>
|
|
||||||
{% endblock %}
|
|
|
@ -1,4 +0,0 @@
|
||||||
<form method="POST" action="/update/">
|
|
||||||
<input type="hidden" name="account_uuid" value="{{ account.uuid }}"></input>
|
|
||||||
<input type="submit" value="Update" class="btn btn-primary"></input>
|
|
||||||
</form>
|
|
52
test.py
52
test.py
|
@ -1,52 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
import requests
|
|
||||||
|
|
||||||
headers = {'Content-Type': 'application/x-ofx'}
|
|
||||||
payload = (
|
|
||||||
"OFXHEADER:100\r\n"
|
|
||||||
"DATA:OFXSGML\r\n"
|
|
||||||
"VERSION:102\r\n"
|
|
||||||
"SECURITY:NONE\r\n"
|
|
||||||
"ENCODING:USASCII\r\n"
|
|
||||||
"CHARSET:1252\r\n"
|
|
||||||
"COMPRESSION:NONE\r\n"
|
|
||||||
"OLDFILEUID:NONE\r\n"
|
|
||||||
"NEWFILEUID:NONE\r\n"
|
|
||||||
"\r\n"
|
|
||||||
"<OFX>\r\n"
|
|
||||||
"<SIGNONMSGSRQV1>\r\n"
|
|
||||||
"<SONRQ>\r\n"
|
|
||||||
"<DTCLIENT>20140427213800.000[-7:MST]\r\n"
|
|
||||||
"<USERID>1242940\r\n"
|
|
||||||
"<USERPASS>0732\r\n"
|
|
||||||
"<LANGUAGE>ENG\r\n"
|
|
||||||
"<FI>\r\n"
|
|
||||||
"<ORG>America First Credit Union\r\n"
|
|
||||||
"<FID>54324\r\n"
|
|
||||||
"</FI>\r\n"
|
|
||||||
"<APPID>QWIN\r\n"
|
|
||||||
"<APPVER>1200\r\n"
|
|
||||||
"</SONRQ>\r\n"
|
|
||||||
"</SIGNONMSGSRQV1>\r\n"
|
|
||||||
"<BANKMSGSRQV1>\r\n"
|
|
||||||
"<STMTTRNRQ>\r\n"
|
|
||||||
"<TRNUID>00000000\r\n"
|
|
||||||
"<STMTRQ>\r\n"
|
|
||||||
"<BANKACCTFROM>\r\n"
|
|
||||||
"<BANKID>324377516\r\n"
|
|
||||||
"<ACCTID>124294-0.9:CHK\r\n"
|
|
||||||
"<ACCTTYPE>CHECKING\r\n"
|
|
||||||
"</BANKACCTFROM>\r\n"
|
|
||||||
"<INCTRAN>\r\n"
|
|
||||||
"<DTSTART>20160101\r\n"
|
|
||||||
"<INCLUDE>Y\r\n"
|
|
||||||
"</INCTRAN>\r\n"
|
|
||||||
"</STMTRQ>\r\n"
|
|
||||||
"</STMTTRNRQ>\r\n"
|
|
||||||
"</BANKMSGSRQV1>\r\n"
|
|
||||||
"</OFX>\r\n")
|
|
||||||
#print(payload)
|
|
||||||
response = requests.post('https://ofx.americafirst.com/', data=payload, headers=headers)
|
|
||||||
print(response.status_code)
|
|
||||||
print(response.headers)
|
|
||||||
print(response.text)
|
|
|
@ -1,9 +0,0 @@
|
||||||
import ofxparse
|
|
||||||
import codecs
|
|
||||||
import io
|
|
||||||
|
|
||||||
with open('tests/files/transactions-2.ofx', 'rb') as f:
|
|
||||||
ofx = ofxparse.OfxParser.parse(f)
|
|
||||||
|
|
||||||
import pdb;pdb.set_trace()
|
|
||||||
print(ofx)
|
|
|
@ -1,182 +0,0 @@
|
||||||
OFXHEADER:100
|
|
||||||
DATA:OFXSGML
|
|
||||||
VERSION:102
|
|
||||||
SECURITY:NONE
|
|
||||||
ENCODING:USASCII
|
|
||||||
CHARSET:1252
|
|
||||||
COMPRESSION:NONE
|
|
||||||
OLDFILEUID:NONE
|
|
||||||
NEWFILEUID:NONE
|
|
||||||
|
|
||||||
<OFX>
|
|
||||||
<SIGNONMSGSRSV1>
|
|
||||||
<SONRS>
|
|
||||||
<STATUS>
|
|
||||||
<CODE>0
|
|
||||||
<SEVERITY>INFO
|
|
||||||
</STATUS>
|
|
||||||
<DTSERVER>20160810225540.951
|
|
||||||
<LANGUAGE>ENG
|
|
||||||
<DTPROFUP>20050531060000.000
|
|
||||||
<FI>
|
|
||||||
<ORG>AFCU
|
|
||||||
<FID>1001
|
|
||||||
</FI>
|
|
||||||
<INTU.BID>54324
|
|
||||||
<INTU.USERID>1234567
|
|
||||||
</SONRS>
|
|
||||||
</SIGNONMSGSRSV1>
|
|
||||||
<BANKMSGSRSV1>
|
|
||||||
<STMTTRNRS>
|
|
||||||
<TRNUID>0
|
|
||||||
<STATUS>
|
|
||||||
<CODE>0
|
|
||||||
<SEVERITY>INFO
|
|
||||||
</STATUS>
|
|
||||||
<STMTRS>
|
|
||||||
<CURDEF>USD
|
|
||||||
<BANKACCTFROM>
|
|
||||||
<BANKID>324377516
|
|
||||||
<ACCTID>1234567~9
|
|
||||||
<ACCTTYPE>CHECKING
|
|
||||||
</BANKACCTFROM>
|
|
||||||
<BANKTRANLIST>
|
|
||||||
<DTSTART>20160801060000.000
|
|
||||||
<DTEND>20160810060000.000
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>CREDIT
|
|
||||||
<DTPOSTED>20160802120000.000
|
|
||||||
<TRNAMT>8410.20
|
|
||||||
<FITID>0006882
|
|
||||||
<NAME>AUTOMATIC DEPOSIT, AUTHENTISE IN
|
|
||||||
<MEMO>AUTOMATIC DEPOSIT, AUTHENTISE INC DIRECT DEP PPD
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160802120000.000
|
|
||||||
<TRNAMT>-95.00
|
|
||||||
<FITID>0006883
|
|
||||||
<NAME>AUTOMATIC WITHDRAWAL, AMERICA FI
|
|
||||||
<MEMO>AUTOMATIC WITHDRAWAL, AMERICA FIRST EXTERNAL TRANSFER, CHRISTINE RIBBLE WEB
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160802120000.000
|
|
||||||
<TRNAMT>-200.00
|
|
||||||
<FITID>0006884
|
|
||||||
<NAME>AUTOMATIC WITHDRAWAL, UESP INVES
|
|
||||||
<MEMO>AUTOMATIC WITHDRAWAL, UESP INVESTMENT WEB(R )
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160802120000.000
|
|
||||||
<TRNAMT>-1943.73
|
|
||||||
<FITID>0006885
|
|
||||||
<NAME>AUTOMATIC WITHDRAWAL, SUNTRUST M
|
|
||||||
<MEMO>AUTOMATIC WITHDRAWAL, SUNTRUST MORTG MTG PMTS WEB(R )
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160803120000.000
|
|
||||||
<TRNAMT>-90.00
|
|
||||||
<FITID>0006886
|
|
||||||
<NAME>ONLINE BANKING FUNDS TRANSFER TO
|
|
||||||
<MEMO>ONLINE BANKING FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>CREDIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>519.68
|
|
||||||
<FITID>0006887
|
|
||||||
<NAME>ONLINE CHECK DEPOSIT
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>-97.00
|
|
||||||
<FITID>0006888
|
|
||||||
<NAME>FUNDS TRANSFER TO SHARE ACCOUNT:
|
|
||||||
<MEMO>FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>-100.00
|
|
||||||
<FITID>0006889
|
|
||||||
<NAME>FUNDS TRANSFER TO SHARE ACCOUNT:
|
|
||||||
<MEMO>FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.1 LUCIANA CIALABRINI
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>-519.68
|
|
||||||
<FITID>0006890
|
|
||||||
<NAME>ONLINE CHECK DEPOSIT
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>CREDIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>519.68
|
|
||||||
<FITID>0006891
|
|
||||||
<NAME>ONLINE CHECK DEPOSIT
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160805120000.000
|
|
||||||
<TRNAMT>-5097.73
|
|
||||||
<FITID>0006892
|
|
||||||
<NAME>AUTOMATIC WITHDRAWAL, DISCOVER E
|
|
||||||
<MEMO>AUTOMATIC WITHDRAWAL, DISCOVER E-PAYMENT WEB(S )
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160806120000.000
|
|
||||||
<TRNAMT>-300.00
|
|
||||||
<FITID>0006893
|
|
||||||
<NAME>WITHDRAWAL
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160808120000.000
|
|
||||||
<TRNAMT>-211.00
|
|
||||||
<FITID>0006894
|
|
||||||
<NAME>ONLINE BANKING FUNDS TRANSFER TO
|
|
||||||
<MEMO>ONLINE BANKING FUNDS TRANSFER TO SHARE ACCOUNT:XXXXXX304-7.9 LUCIANA CIALABRINI
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>DEBIT
|
|
||||||
<DTPOSTED>20160808120000.000
|
|
||||||
<TRNAMT>-293.00
|
|
||||||
<FITID>0006895
|
|
||||||
<NAME>AUTOMATIC WITHDRAWAL, CHALLENGER
|
|
||||||
<MEMO>AUTOMATIC WITHDRAWAL, CHALLENGER SCHOOTUITION PPD
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>CHECK
|
|
||||||
<DTPOSTED>20160809120000.000
|
|
||||||
<TRNAMT>-200.00
|
|
||||||
<FITID>0006896
|
|
||||||
<CHECKNUM>191
|
|
||||||
<NAME>CHECK # 191
|
|
||||||
</STMTTRN>
|
|
||||||
<STMTTRN>
|
|
||||||
<TRNTYPE>CHECK
|
|
||||||
<DTPOSTED>20160810120000.000
|
|
||||||
<TRNAMT>-270.00
|
|
||||||
<FITID>0006897
|
|
||||||
<CHECKNUM>192
|
|
||||||
<NAME>CHECK # 192
|
|
||||||
</STMTTRN>
|
|
||||||
</BANKTRANLIST>
|
|
||||||
<LEDGERBAL>
|
|
||||||
<BALAMT>26882.49
|
|
||||||
<DTASOF>20160810225540.951
|
|
||||||
</LEDGERBAL>
|
|
||||||
<AVAILBAL>
|
|
||||||
<BALAMT>26882.49
|
|
||||||
<DTASOF>20160810225540.951
|
|
||||||
</AVAILBAL>
|
|
||||||
</STMTRS>
|
|
||||||
</STMTTRNRS>
|
|
||||||
</BANKMSGSRSV1>
|
|
||||||
</OFX>
|
|
|
@ -1,29 +0,0 @@
|
||||||
OFXHEADER:100
|
|
||||||
DATA:OFXSGML
|
|
||||||
VERSION:102
|
|
||||||
SECURITY:NONE
|
|
||||||
ENCODING:USASCII
|
|
||||||
CHARSET:1252
|
|
||||||
COMPRESSION:NONE
|
|
||||||
OLDFILEUID:NONE
|
|
||||||
NEWFILEUID:NONE
|
|
||||||
|
|
||||||
<OFX>
|
|
||||||
<SIGNONMSGSRSV1>
|
|
||||||
<SONRS>
|
|
||||||
<STATUS>
|
|
||||||
<CODE>0
|
|
||||||
<SEVERITY>INFO
|
|
||||||
</STATUS>
|
|
||||||
<DTSERVER>20160810225540.951
|
|
||||||
<LANGUAGE>ENG
|
|
||||||
<DTPROFUP>20050531060000.000
|
|
||||||
<FI>
|
|
||||||
<ORG>AFCU
|
|
||||||
<FID>1001
|
|
||||||
</FI>
|
|
||||||
<INTU.BID>54324
|
|
||||||
<INTU.USERID>1242940
|
|
||||||
</SONRS>
|
|
||||||
</SIGNONMSGSRSV1>
|
|
||||||
</OFX>
|
|
83
tests/test_ofx.py
Normal file
83
tests/test_ofx.py
Normal file
|
@ -0,0 +1,83 @@
|
||||||
|
import datetime
|
||||||
|
|
||||||
|
import vanth.ofx
|
||||||
|
|
||||||
|
|
||||||
|
def MST():
|
||||||
|
return datetime.timezone(datetime.timedelta(hours=-7), 'MST')
|
||||||
|
|
||||||
|
def MDT():
|
||||||
|
return datetime.timezone(datetime.timedelta(hours=-6), 'MDT')
|
||||||
|
|
||||||
|
def test_query_transactions(mocker):
|
||||||
|
institution = {
|
||||||
|
'bankid' : "1234567",
|
||||||
|
'fid' : "12345",
|
||||||
|
'name' : "AFCU",
|
||||||
|
}
|
||||||
|
account = {
|
||||||
|
"account_id" : "123456-0.9:CHK",
|
||||||
|
"user_id" : "123456789",
|
||||||
|
"password" : "1234",
|
||||||
|
"type" : "checking",
|
||||||
|
}
|
||||||
|
with mocker.patch('vanth.ofx.now', return_value='20160102030405.000[-7:MST]'):
|
||||||
|
results = vanth.ofx.query_transactions(institution, account, start=datetime.date(2016, 1, 2))
|
||||||
|
with open('tests/files/query_transactions.ofx', 'rb') as f:
|
||||||
|
expected = f.read().decode('utf-8')
|
||||||
|
assert results == expected
|
||||||
|
|
||||||
|
def test_parse():
|
||||||
|
with open('tests/files/transactions.ofx', 'rb') as f:
|
||||||
|
transactions = f.read().decode('utf-8')
|
||||||
|
document = vanth.ofx.parse(transactions)
|
||||||
|
assert document.header == {
|
||||||
|
'CHARSET' : '1252',
|
||||||
|
'COMPRESSION' : 'NONE',
|
||||||
|
'DATA' : 'OFXSGML',
|
||||||
|
'ENCODING' : 'USASCII',
|
||||||
|
'NEWFILEUID' : 'NONE',
|
||||||
|
'OFXHEADER' : '100',
|
||||||
|
'OLDFILEUID' : 'NONE',
|
||||||
|
'SECURITY' : 'NONE',
|
||||||
|
'VERSION' : '102'
|
||||||
|
}
|
||||||
|
assert document.body.status.code == '0'
|
||||||
|
assert document.body.status.severity == 'INFO'
|
||||||
|
assert document.body.status.message == 'The operation succeeded.'
|
||||||
|
assert document.body.statement.status.code == '0'
|
||||||
|
assert document.body.statement.status.severity == 'INFO'
|
||||||
|
assert document.body.statement.status.message is None
|
||||||
|
assert document.body.statement.transactions.currency == 'USD'
|
||||||
|
assert document.body.statement.transactions.account.accountid == '123456-0.9:CHK'
|
||||||
|
assert document.body.statement.transactions.account.bankid == '324377516'
|
||||||
|
assert document.body.statement.transactions.account.type == 'CHECKING'
|
||||||
|
assert document.body.statement.transactions.start == datetime.datetime(2015, 12, 31, 17, 0, tzinfo=MST())
|
||||||
|
assert document.body.statement.transactions.end == datetime.datetime(2016, 6, 22, 11, 12, 42, tzinfo=MDT())
|
||||||
|
expected_items = [{
|
||||||
|
'amount' : -50.19,
|
||||||
|
'available' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'id' : '0006547',
|
||||||
|
'memo' : 'POINT OF SALE PURCHASE #0006547',
|
||||||
|
'name' : 'UT LEHI COSTCO WHSE #0733',
|
||||||
|
'posted' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'type' : 'POS',
|
||||||
|
},{
|
||||||
|
'amount' : -79.64,
|
||||||
|
'available' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'id' : '0006548',
|
||||||
|
'memo' : '#0006548',
|
||||||
|
'name' : 'Payment to PACIFICORP ONLIN',
|
||||||
|
'posted' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'type' : 'PAYMENT',
|
||||||
|
},{
|
||||||
|
'amount' : 0.84,
|
||||||
|
'available' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'id' : '0006549',
|
||||||
|
'memo' : 'ANNUAL PERCENTAGE YIELD EARNED IS .05% #0006549',
|
||||||
|
'name' : 'DIVIDEND FOR 12/01/15 - 12/31/1',
|
||||||
|
'posted' : datetime.datetime(2015, 12, 31, 12),
|
||||||
|
'type' : 'INT',
|
||||||
|
}]
|
||||||
|
items = [dict(item) for item in document.body.statement.transactions.items]
|
||||||
|
assert items == expected_items
|
17
tests/test_sgml.py
Normal file
17
tests/test_sgml.py
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
import vanth.sgml
|
||||||
|
|
||||||
|
|
||||||
|
def child_values(node):
|
||||||
|
return [(child.name, child.value) for child in node.children]
|
||||||
|
|
||||||
|
def test_siblings():
|
||||||
|
result = vanth.sgml.parse("<A><B><C>1<D>2<E>3</B></A>")
|
||||||
|
assert result.name == 'A'
|
||||||
|
assert child_values(result['B']) == [('C', '1'), ('D', '2'), ('E', '3')]
|
||||||
|
|
||||||
|
def test_closing():
|
||||||
|
result = vanth.sgml.parse("<A><B><C>1</B><D><E>2</D></A>")
|
||||||
|
assert result.name == 'A'
|
||||||
|
assert child_values(result) == [('B', ''), ('D', '')]
|
||||||
|
assert child_values(result['B']) == [('C', '1')]
|
||||||
|
assert child_values(result['D']) == [('E', '2')]
|
File diff suppressed because it is too large
Load diff
|
@ -1,12 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<institution id="533">
|
|
||||||
<name>America First Credit Union</name>
|
|
||||||
<fid>54324</fid>
|
|
||||||
<org>America First Credit Union</org>
|
|
||||||
<url>https://ofx.americafirst.com</url>
|
|
||||||
<ofxfail>0</ofxfail>
|
|
||||||
<sslfail>0</sslfail>
|
|
||||||
<lastofxvalidation>2016-05-18 01:02:37</lastofxvalidation>
|
|
||||||
<lastsslvalidation>2016-05-18 01:04:01</lastsslvalidation>
|
|
||||||
<profile addr1="PO Box 9199" city="Ogden" state="UT" postalcode="84409" country="USA" csphone="800.999.3961" tsphone="866.224.2158" url="http://www.americafirst.com" email="support@americafirst.com" signonmsgset="true" bankmsgset="true" creditcardmsgset="true"/>
|
|
||||||
</institution>
|
|
|
@ -1,43 +0,0 @@
|
||||||
OFXHEADER:100
|
|
||||||
DATA:OFXSGML
|
|
||||||
VERSION:102
|
|
||||||
SECURITY:NONE
|
|
||||||
ENCODING:USASCII
|
|
||||||
CHARSET:1252
|
|
||||||
COMPRESSION:NONE
|
|
||||||
OLDFILEUID:NONE
|
|
||||||
NEWFILEUID:NONE
|
|
||||||
|
|
||||||
<OFX>
|
|
||||||
<SIGNONMSGSRQV1>
|
|
||||||
<SONRQ>
|
|
||||||
<DTCLIENT>20160621180047.000[-7:MST]
|
|
||||||
<USERID>1242940
|
|
||||||
<USERPASS>0732
|
|
||||||
<LANGUAGE>ENG
|
|
||||||
<FI>
|
|
||||||
<ORG>America First Credit Union
|
|
||||||
<FID>54324
|
|
||||||
</FI>
|
|
||||||
<APPID>QWIN
|
|
||||||
<APPVER>1200
|
|
||||||
</SONRQ>
|
|
||||||
</SIGNONMSGSRQV1>
|
|
||||||
<BANKMSGSRQV1>
|
|
||||||
<STMTTRNRQ>
|
|
||||||
<TRNUID>00000000
|
|
||||||
<STMTRQ>
|
|
||||||
<BANKACCTFROM>
|
|
||||||
<BANKID>324377516
|
|
||||||
<ACCTID>124294-0.9:CHK
|
|
||||||
<ACCTTYPE>CHECKING
|
|
||||||
</BANKACCTFROM>
|
|
||||||
<INCTRAN>
|
|
||||||
<DTSTART>20140101
|
|
||||||
<INCLUDE>Y
|
|
||||||
</INCTRAN>
|
|
||||||
</STMTRQ>
|
|
||||||
</STMTTRNRQ>
|
|
||||||
</BANKMSGSRQV1>
|
|
||||||
</OFX>
|
|
||||||
|
|
|
@ -1,43 +0,0 @@
|
||||||
OFXHEADER:100
|
|
||||||
DATA:OFXSGML
|
|
||||||
VERSION:102
|
|
||||||
SECURITY:NONE
|
|
||||||
ENCODING:USASCII
|
|
||||||
CHARSET:1252
|
|
||||||
COMPRESSION:NONE
|
|
||||||
OLDFILEUID:NONE
|
|
||||||
NEWFILEUID:NONE
|
|
||||||
|
|
||||||
<OFX>
|
|
||||||
<SIGNONMSGSRQV1>
|
|
||||||
<SONRQ>
|
|
||||||
<DTCLIENT>20140427213800.000[-7:MST]
|
|
||||||
<USERID>1242940
|
|
||||||
<USERPASS>0732
|
|
||||||
<LANGUAGE>ENG
|
|
||||||
<FI>
|
|
||||||
<ORG>America First Credit Union
|
|
||||||
<FID>54324
|
|
||||||
</FI>
|
|
||||||
<APPID>QWIN
|
|
||||||
<APPVER>1200
|
|
||||||
</SONRQ>
|
|
||||||
</SIGNONMSGSRQV1>
|
|
||||||
<BANKMSGSRQV1>
|
|
||||||
<STMTTRNRQ>
|
|
||||||
<TRNUID>00000000
|
|
||||||
<STMTRQ>
|
|
||||||
<BANKACCTFROM>
|
|
||||||
<BANKID>324377516
|
|
||||||
<ACCTID>124294-0.9:CHK
|
|
||||||
<ACCTTYPE>CHECKING
|
|
||||||
</BANKACCTFROM>
|
|
||||||
<INCTRAN>
|
|
||||||
<DTSTART>20160101
|
|
||||||
<INCLUDE>Y
|
|
||||||
</INCTRAN>
|
|
||||||
</STMTRQ>
|
|
||||||
</STMTTRNRQ>
|
|
||||||
</BANKMSGSRQV1>
|
|
||||||
</OFX>
|
|
||||||
|
|
|
@ -1,407 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<institutionlist>
|
|
||||||
<institutionid name="121 Financial Credit Union" id="666"/>
|
|
||||||
<institutionid name="1st Advantage FCU" id="542"/>
|
|
||||||
<institutionid name="5 Star Bank" id="774"/>
|
|
||||||
<institutionid name="66 Federal Credit Union" id="553"/>
|
|
||||||
<institutionid name="A. G. Edwards and Sons, Inc." id="491"/>
|
|
||||||
<institutionid name="Abbott Laboratories Employee CU" id="667"/>
|
|
||||||
<institutionid name="ABNB Federal Credit Union" id="530"/>
|
|
||||||
<institutionid name="Achieva Credit Union" id="668"/>
|
|
||||||
<institutionid name="Addison Avenue Federal Credit Union" id="550"/>
|
|
||||||
<institutionid name="Advantis Credit Union" id="782"/>
|
|
||||||
<institutionid name="Affinity Plus Federal Credit Union" id="732"/>
|
|
||||||
<institutionid name="Affinity Plus Federal Credit Union-New" id="810"/>
|
|
||||||
<institutionid name="AIM Investments" id="497"/>
|
|
||||||
<institutionid name="Alaska Air Visa (Bank of America)" id="796"/>
|
|
||||||
<institutionid name="Allegiance Credit Union" id="531"/>
|
|
||||||
<institutionid name="Alpine Banks of Colorado" id="643"/>
|
|
||||||
<institutionid name="AltaOne" id="772"/>
|
|
||||||
<institutionid name="AltaOne Federal Credit Union" id="490"/>
|
|
||||||
<institutionid name="AmegyBank" id="786"/>
|
|
||||||
<institutionid name="America First Credit Union" id="533"/>
|
|
||||||
<institutionid name="American Century Investments" id="739"/>
|
|
||||||
<institutionid name="American Express Card" id="424"/>
|
|
||||||
<institutionid name="American Funds" id="590"/>
|
|
||||||
<institutionid name="American National Bank" id="669"/>
|
|
||||||
<institutionid name="Ameriprise Financial Services, Inc." id="489"/>
|
|
||||||
<institutionid name="Amplify Federal Credit Union" id="788"/>
|
|
||||||
<institutionid name="Andrews Federal Credit Union" id="670"/>
|
|
||||||
<institutionid name="APCO EMPLOYEES CREDIT UNION" id="521"/>
|
|
||||||
<institutionid name="Apple FCU" id="746"/>
|
|
||||||
<institutionid name="Ariel Mutual Funds" id="566"/>
|
|
||||||
<institutionid name="Arizona Federal Credit Union" id="637"/>
|
|
||||||
<institutionid name="Arizona State Credit Union" id="785"/>
|
|
||||||
<institutionid name="Ascentra Credit Union" id="423"/>
|
|
||||||
<institutionid name="AT&T Universal Card" id="427"/>
|
|
||||||
<institutionid name="AXA Equitable" id="763"/>
|
|
||||||
<institutionid name="B-M S Federal Credit Union" id="505"/>
|
|
||||||
<institutionid name="BancFirst" id="644"/>
|
|
||||||
<institutionid name="Bancorpsouth" id="803"/>
|
|
||||||
<institutionid name="Bank of America" id="639"/>
|
|
||||||
<institutionid name="Bank of America (All except CA, WA,&ID)" id="472"/>
|
|
||||||
<institutionid name="Bank of America (California)" id="635"/>
|
|
||||||
<institutionid name="Bank of America (Formerly Fleet)" id="674"/>
|
|
||||||
<institutionid name="Bank of America - 5959" id="765"/>
|
|
||||||
<institutionid name="Bank of America - access.ofx" id="790"/>
|
|
||||||
<institutionid name="Bank Of America(All except CA,WA,&ID " id="661"/>
|
|
||||||
<institutionid name="Bank of George" id="733"/>
|
|
||||||
<institutionid name="Bank of Internet, USA" id="787"/>
|
|
||||||
<institutionid name="Bank of Stockton" id="429"/>
|
|
||||||
<institutionid name="Bank of Tampa, The" id="522"/>
|
|
||||||
<institutionid name="Bank of the Cascades" id="430"/>
|
|
||||||
<institutionid name="Bank of the West" id="656"/>
|
|
||||||
<institutionid name="Bank One" id="428"/>
|
|
||||||
<institutionid name="Bank One (Chicago)" id="672"/>
|
|
||||||
<institutionid name="Bank One (Michigan and Florida)" id="673"/>
|
|
||||||
<institutionid name="Bank-Fund Staff FCU" id="520"/>
|
|
||||||
<institutionid name="BankBoston PC Banking" id="675"/>
|
|
||||||
<institutionid name="bankfinancial" id="762"/>
|
|
||||||
<institutionid name="Baton Rouge City Parish Emp FCU" id="576"/>
|
|
||||||
<institutionid name="BB&T" id="475"/>
|
|
||||||
<institutionid name="Belmont Savings Bank" id="775"/>
|
|
||||||
<institutionid name="Bernstein Global Wealth Mgmt" id="605"/>
|
|
||||||
<institutionid name="Beverly Co-Operative Bank" id="676"/>
|
|
||||||
<institutionid name="Billings Federal Credit Union" id="603"/>
|
|
||||||
<institutionid name="Boeing Employees Credit Union" id="647"/>
|
|
||||||
<institutionid name="Bofi federal bank" id="798"/>
|
|
||||||
<institutionid name="Bossier Federal Credit Union" id="611"/>
|
|
||||||
<institutionid name="California Bank&Trust" id="487"/>
|
|
||||||
<institutionid name="Cambridge Portuguese Credit Union" id="677"/>
|
|
||||||
<institutionid name="Cambridge Savings Bank" id="760"/>
|
|
||||||
<institutionid name="Camino FCU" id="580"/>
|
|
||||||
<institutionid name="Campus USA Credit Union" id="546"/>
|
|
||||||
<institutionid name="Capital One" id="628"/>
|
|
||||||
<institutionid name="Capital One 360" id="783"/>
|
|
||||||
<institutionid name="Capital One Bank" id="631"/>
|
|
||||||
<institutionid name="Capital One Bank (after 12-15-13)" id="802"/>
|
|
||||||
<institutionid name="Capital One Bank - 2" id="648"/>
|
|
||||||
<institutionid name="Capitol Federal Savings Bank" id="789"/>
|
|
||||||
<institutionid name="Cedar Point Federal Credit Union" id="523"/>
|
|
||||||
<institutionid name="CenterState Bank" id="773"/>
|
|
||||||
<institutionid name="Centra Credit Union" id="431"/>
|
|
||||||
<institutionid name="Centra Credit Union2" id="662"/>
|
|
||||||
<institutionid name="Central Bank Utah" id="820"/>
|
|
||||||
<institutionid name="Central Florida Educators FCU" id="486"/>
|
|
||||||
<institutionid name="Central Maine FCU" id="543"/>
|
|
||||||
<institutionid name="Centura Bank" id="432"/>
|
|
||||||
<institutionid name="Century Federal Credit Union" id="529"/>
|
|
||||||
<institutionid name="Charles Schwab Bank, N.A." id="578"/>
|
|
||||||
<institutionid name="Charles Schwab Retirement" id="729"/>
|
|
||||||
<institutionid name="Charles Schwab Retirement Plan Services" id="730"/>
|
|
||||||
<institutionid name="Charles Schwab&Co., INC" id="433"/>
|
|
||||||
<institutionid name="Chase (credit card) " id="636"/>
|
|
||||||
<institutionid name="Chemical Bank" id="747"/>
|
|
||||||
<institutionid name="Chesterfield Federal Credit Union" id="545"/>
|
|
||||||
<institutionid name="Chicago Patrolmens FCU" id="517"/>
|
|
||||||
<institutionid name="Citadel FCU" id="477"/>
|
|
||||||
<institutionid name="Citi Credit Card" id="629"/>
|
|
||||||
<institutionid name="Citi Personal Wealth Management" id="671"/>
|
|
||||||
<institutionid name="Citibank" id="678"/>
|
|
||||||
<institutionid name="Citizens Bank" id="664"/>
|
|
||||||
<institutionid name="Citizens Bank - Business" id="528"/>
|
|
||||||
<institutionid name="Citizens Bank - Consumer" id="527"/>
|
|
||||||
<institutionid name="Citizens National Bank" id="768"/>
|
|
||||||
<institutionid name="Clearview Federal Credit Union" id="478"/>
|
|
||||||
<institutionid name="Collegedale Credit Union" id="496"/>
|
|
||||||
<institutionid name="Colonial Bank" id="436"/>
|
|
||||||
<institutionid name="Columbia Credit Union" id="541"/>
|
|
||||||
<institutionid name="Comerica Bank" id="437"/>
|
|
||||||
<institutionid name="Commerce Bank" id="640"/>
|
|
||||||
<institutionid name="Commerce Bank NJ, PA, NY&DE" id="438"/>
|
|
||||||
<institutionid name="Commerce Bank, NA" id="439"/>
|
|
||||||
<institutionid name="Commercial Federal Bank" id="440"/>
|
|
||||||
<institutionid name="Community 1st Credit Union" id="738"/>
|
|
||||||
<institutionid name="Community Bank, N.A." id="679"/>
|
|
||||||
<institutionid name="Community First Credit Union" id="592"/>
|
|
||||||
<institutionid name="Community Resource Bank" id="600"/>
|
|
||||||
<institutionid name="CompassPC" id="824"/>
|
|
||||||
<institutionid name="COMSTAR FCU" id="441"/>
|
|
||||||
<institutionid name="Consumers Credit Union" id="680"/>
|
|
||||||
<institutionid name="Continental Federal Credit Union" id="555"/>
|
|
||||||
<institutionid name="CPM Federal Credit Union" id="681"/>
|
|
||||||
<institutionid name="Credit Suisse Securities USA LLC" id="753"/>
|
|
||||||
<institutionid name="Credit Union 1 - IL" id="610"/>
|
|
||||||
<institutionid name="Credit Union ONE" id="618"/>
|
|
||||||
<institutionid name="Cyprus Federal Credit Union" id="569"/>
|
|
||||||
<institutionid name="D. A. Davidson" id="805"/>
|
|
||||||
<institutionid name="DATCU" id="682"/>
|
|
||||||
<institutionid name="Day Air Credit Union" id="585"/>
|
|
||||||
<institutionid name="Delta Community Credit Union" id="573"/>
|
|
||||||
<institutionid name="Denali Alaskan FCU" id="443"/>
|
|
||||||
<institutionid name="Denver Community Federal Credit Union" id="683"/>
|
|
||||||
<institutionid name="Desert Schools Federal Credit Union" id="645"/>
|
|
||||||
<institutionid name="Discover Bank" id="726"/>
|
|
||||||
<institutionid name="Discover Card" id="444"/>
|
|
||||||
<institutionid name="Discover Platinum" id="684"/>
|
|
||||||
<institutionid name="Dodge&Cox Funds" id="621"/>
|
|
||||||
<institutionid name="Dominion Credit Union" id="561"/>
|
|
||||||
<institutionid name="Dreyfus" id="445"/>
|
|
||||||
<institutionid name="Dupaco Community Credit Union" id="519"/>
|
|
||||||
<institutionid name="DuPont Community Credit Union" id="485"/>
|
|
||||||
<institutionid name="E*TRADE" id="446"/>
|
|
||||||
<institutionid name="EAB" id="685"/>
|
|
||||||
<institutionid name="Eastern Bank" id="447"/>
|
|
||||||
<institutionid name="EDS Credit Union" id="448"/>
|
|
||||||
<institutionid name="Educational Employees CU Fresno" id="492"/>
|
|
||||||
<institutionid name="Edward Jones" id="608"/>
|
|
||||||
<institutionid name="Elevations Credit Union" id="727"/>
|
|
||||||
<institutionid name="Elevations Credit Union IB WC-DC" id="752"/>
|
|
||||||
<institutionid name="Envision Credit Union" id="540"/>
|
|
||||||
<institutionid name="FAA Credit Union" id="686"/>
|
|
||||||
<institutionid name="FAA Technical Center FCU" id="583"/>
|
|
||||||
<institutionid name="Fairwinds Credit Union" id="687"/>
|
|
||||||
<institutionid name="Fall River Municipal CU" id="559"/>
|
|
||||||
<institutionid name="FedChoice FCU" id="688"/>
|
|
||||||
<institutionid name="Fidelity Investments" id="449"/>
|
|
||||||
<institutionid name="Fidelity NetBenefits" id="558"/>
|
|
||||||
<institutionid name="Fifth Third Bancorp" id="450"/>
|
|
||||||
<institutionid name="Finance Center FCU (IN)" id="535"/>
|
|
||||||
<institutionid name="Financial Center CU" id="548"/>
|
|
||||||
<institutionid name="First Alliance Credit Union" id="602"/>
|
|
||||||
<institutionid name="First Citizens" id="690"/>
|
|
||||||
<institutionid name="First Citizens Bank - NC, VA, WV" id="480"/>
|
|
||||||
<institutionid name="First Clearing, LLC" id="689"/>
|
|
||||||
<institutionid name="First Command Bank" id="766"/>
|
|
||||||
<institutionid name="First Commonwealth FCU" id="488"/>
|
|
||||||
<institutionid name="First Community FCU" id="515"/>
|
|
||||||
<institutionid name="First Florida Credit Union" id="612"/>
|
|
||||||
<institutionid name="First Hawaiian Bank" id="691"/>
|
|
||||||
<institutionid name="First Internet Bank of Indiana" id="642"/>
|
|
||||||
<institutionid name="First Interstate Bank" id="693"/>
|
|
||||||
<institutionid name="First National Bank (Texas)" id="655"/>
|
|
||||||
<institutionid name="First National Bank of St. Louis" id="692"/>
|
|
||||||
<institutionid name="First Republic Bank" id="770"/>
|
|
||||||
<institutionid name="First Southwest Company" id="620"/>
|
|
||||||
<institutionid name="First Tech Credit Union" id="451"/>
|
|
||||||
<institutionid name="First Tech Federal Credit Union" id="731"/>
|
|
||||||
<institutionid name="First Tennessee" id="795"/>
|
|
||||||
<institutionid name="Firstar" id="494"/>
|
|
||||||
<institutionid name="FirstBank of Colorado" id="554"/>
|
|
||||||
<institutionid name="FivePoint Credit Union" id="599"/>
|
|
||||||
<institutionid name="Flagstar Bank" id="784"/>
|
|
||||||
<institutionid name="Florida Telco CU" id="484"/>
|
|
||||||
<institutionid name="Fort Knox Federal Credit Union" id="536"/>
|
|
||||||
<institutionid name="Fort Stewart GeorgiaFCU" id="506"/>
|
|
||||||
<institutionid name="Franklin Templeton Investments" id="734"/>
|
|
||||||
<institutionid name="Fremont Bank" id="556"/>
|
|
||||||
<institutionid name="GCS Federal Credit Union" id="498"/>
|
|
||||||
<institutionid name="Goldman Sachs" id="694"/>
|
|
||||||
<institutionid name="Haverhill Bank" id="757"/>
|
|
||||||
<institutionid name="Hawaii State FCU" id="588"/>
|
|
||||||
<institutionid name="Hawaiian Tel Federal Credit Union" id="549"/>
|
|
||||||
<institutionid name="Hawthorne Credit Union" id="493"/>
|
|
||||||
<institutionid name="Hewitt Associates LLC" id="572"/>
|
|
||||||
<institutionid name="HFS Federal Credit Union" id="562"/>
|
|
||||||
<institutionid name="Home Federal Savings Bank(MN/IA)" id="594"/>
|
|
||||||
<institutionid name="Hudson Valley FCU" id="695"/>
|
|
||||||
<institutionid name="Hughes Federal Credit Union" id="745"/>
|
|
||||||
<institutionid name="Huntington National Bank" id="574"/>
|
|
||||||
<institutionid name="IBM Southeast Employees Federal Credit Union" id="696"/>
|
|
||||||
<institutionid name="Iinvestor360" id="792"/>
|
|
||||||
<institutionid name="ING DIRECT" id="658"/>
|
|
||||||
<institutionid name="ING DIRECT (Canada)" id="421"/>
|
|
||||||
<institutionid name="ING Institutional Plan Services " id="735"/>
|
|
||||||
<institutionid name="Insight CU" id="697"/>
|
|
||||||
<institutionid name="Institution For Savings" id="816"/>
|
|
||||||
<institutionid name="International Bank of Commerce" id="743"/>
|
|
||||||
<institutionid name="IronStone Bank" id="563"/>
|
|
||||||
<institutionid name="J.P. Morgan" id="700"/>
|
|
||||||
<institutionid name="J.P. Morgan Clearing Corp." id="701"/>
|
|
||||||
<institutionid name="J.P. Morgan Private Banking" id="740"/>
|
|
||||||
<institutionid name="Janney Montgomery Scott LLC" id="698"/>
|
|
||||||
<institutionid name="Janus" id="615"/>
|
|
||||||
<institutionid name="JPMorgan Chase Bank" id="435"/>
|
|
||||||
<institutionid name="JPMorgan Chase Bank (Texas)" id="434"/>
|
|
||||||
<institutionid name="JPMorgan Retirement Plan Services" id="617"/>
|
|
||||||
<institutionid name="JSC Federal Credit Union" id="699"/>
|
|
||||||
<institutionid name="Kennedy Space Center FCU" id="501"/>
|
|
||||||
<institutionid name="KeyBank" id="453"/>
|
|
||||||
<institutionid name="Kinecta Federal Credit Union" id="646"/>
|
|
||||||
<institutionid name="Kirtland Federal Credit Union" id="544"/>
|
|
||||||
<institutionid name="Kitsap Community Credit Union" id="728"/>
|
|
||||||
<institutionid name="La Banque Postale" id="813"/>
|
|
||||||
<institutionid name="Landings Credit Union" id="822"/>
|
|
||||||
<institutionid name="Las Colinas FCU" id="524"/>
|
|
||||||
<institutionid name="LaSalle Bank Midwest" id="455"/>
|
|
||||||
<institutionid name="LaSalle Bank NA" id="474"/>
|
|
||||||
<institutionid name="Local Government Federal Credit Union" id="748"/>
|
|
||||||
<institutionid name="Los Alamos National Bank" id="476"/>
|
|
||||||
<institutionid name="M & T Bank" id="702"/>
|
|
||||||
<institutionid name="Mainline National Bank" id="663"/>
|
|
||||||
<institutionid name="Marquette Banks" id="703"/>
|
|
||||||
<institutionid name="Mayo Employees Federal Credit Union" id="598"/>
|
|
||||||
<institutionid name="McCoy Federal Credit Union" id="525"/>
|
|
||||||
<institutionid name="Mellon Bank" id="454"/>
|
|
||||||
<institutionid name="Mercantile Brokerage Services" id="461"/>
|
|
||||||
<institutionid name="Mercer" id="704"/>
|
|
||||||
<institutionid name="Merck Sharp&Dohme FCU" id="609"/>
|
|
||||||
<institutionid name="Merrill Lynch Online Payment" id="705"/>
|
|
||||||
<institutionid name="Merrill Lynch&Co., Inc." id="510"/>
|
|
||||||
<institutionid name="Metro Bank" id="654"/>
|
|
||||||
<institutionid name="Michigan State University Federal CU" id="649"/>
|
|
||||||
<institutionid name="Mission Federal Credit Union" id="758"/>
|
|
||||||
<institutionid name="Missoula Federal Credit Union" id="706"/>
|
|
||||||
<institutionid name="Monterey Credit Union" id="804"/>
|
|
||||||
<institutionid name="Morgan Stanley (Smith Barney)" id="707"/>
|
|
||||||
<institutionid name="Morgan Stanley ClientServ" id="500"/>
|
|
||||||
<institutionid name="Morgan Stanley ClientServ - Quicken Win Format" id="806"/>
|
|
||||||
<institutionid name="Motorola Employees Credit Union" id="534"/>
|
|
||||||
<institutionid name="Mountain America Credit Union" id="657"/>
|
|
||||||
<institutionid name="MTC Federal Credit Union" id="593"/>
|
|
||||||
<institutionid name="Municipal Employees Credit Union of Baltimore, Inc." id="584"/>
|
|
||||||
<institutionid name="Mutual Bank" id="809"/>
|
|
||||||
<institutionid name="myStreetscape" id="495"/>
|
|
||||||
<institutionid name="Nantucket Bank" id="456"/>
|
|
||||||
<institutionid name="National City" id="627"/>
|
|
||||||
<institutionid name="National Penn Bank" id="457"/>
|
|
||||||
<institutionid name="Navy Army Federal Credit Union" id="551"/>
|
|
||||||
<institutionid name="NetxClient UAT" id="761"/>
|
|
||||||
<institutionid name="Nevada Federal Credit Union" id="552"/>
|
|
||||||
<institutionid name="Nevada State Bank - New" id="458"/>
|
|
||||||
<institutionid name="Nevada State Bank - OLD" id="708"/>
|
|
||||||
<institutionid name="New England Federal Credit Union" id="709"/>
|
|
||||||
<institutionid name="North Carolina State Employees Credit Union" id="742"/>
|
|
||||||
<institutionid name="North Carolina State Employees' Credit Union" id="632"/>
|
|
||||||
<institutionid name="North Country FCU" id="754"/>
|
|
||||||
<institutionid name="NorthEast Alliance FCU" id="613"/>
|
|
||||||
<institutionid name="Northern Trust - Banking" id="481"/>
|
|
||||||
<institutionid name="Northern Trust - Investments" id="507"/>
|
|
||||||
<institutionid name="Northwest Community CU" id="741"/>
|
|
||||||
<institutionid name="Norwest" id="710"/>
|
|
||||||
<institutionid name="Novartis Federal Credit Union" id="581"/>
|
|
||||||
<institutionid name="nuVision Financial FCU" id="821"/>
|
|
||||||
<institutionid name="NW Preferred Federal Credit Union" id="579"/>
|
|
||||||
<institutionid name="OCTFCU" id="587"/>
|
|
||||||
<institutionid name="Old National Bank" id="526"/>
|
|
||||||
<institutionid name="Oppenheimer & Co. Inc." id="711"/>
|
|
||||||
<institutionid name="OptionsXpress, Inc" id="565"/>
|
|
||||||
<institutionid name="Oregon College Savings Plan" id="712"/>
|
|
||||||
<institutionid name="Oregon Community Credit Union" id="781"/>
|
|
||||||
<institutionid name="Patelco CU" id="460"/>
|
|
||||||
<institutionid name="Patriots Federal Credit Union" id="596"/>
|
|
||||||
<institutionid name="Peninsula Community Federal Credit Union" id="557"/>
|
|
||||||
<institutionid name="Pennsylvania State Employees Credit Union" id="814"/>
|
|
||||||
<institutionid name="Penson Financial Services" id="570"/>
|
|
||||||
<institutionid name="Picatinny Federal Credit Union" id="508"/>
|
|
||||||
<institutionid name="PNC Bank" id="634"/>
|
|
||||||
<institutionid name="PNC Banking Online" id="818"/>
|
|
||||||
<institutionid name="PNC Online Banking" id="817"/>
|
|
||||||
<institutionid name="Premier America Credit Union" id="764"/>
|
|
||||||
<institutionid name="Premier Members FCU" id="823"/>
|
|
||||||
<institutionid name="Prudential Retirement" id="567"/>
|
|
||||||
<institutionid name="PSECU" id="539"/>
|
|
||||||
<institutionid name="RaboBank America" id="744"/>
|
|
||||||
<institutionid name="RBC Dain Rauscher" id="713"/>
|
|
||||||
<institutionid name="Red Crown Federal Credit Union" id="504"/>
|
|
||||||
<institutionid name="Redstone Federal Credit Union" id="633"/>
|
|
||||||
<institutionid name="Regions Bank" id="462"/>
|
|
||||||
<institutionid name="Reliant Community Credit Union" id="595"/>
|
|
||||||
<institutionid name="Robert W. Baird & Co." id="714"/>
|
|
||||||
<institutionid name="Royce&Associates" id="589"/>
|
|
||||||
<institutionid name="SAC FEDERAL CREDIT UNION" id="509"/>
|
|
||||||
<institutionid name="Sacramento Credit Union" id="651"/>
|
|
||||||
<institutionid name="Safe Credit Union - OFX Beta" id="422"/>
|
|
||||||
<institutionid name="SafeAmerica Credit Union" id="597"/>
|
|
||||||
<institutionid name="Salt Lake City Credit Union" id="619"/>
|
|
||||||
<institutionid name="Sandia Laboratory Federal Credit Union" id="780"/>
|
|
||||||
<institutionid name="Santa Barbara Bank & Trust" id="659"/>
|
|
||||||
<institutionid name="Schools Financial Credit Union" id="577"/>
|
|
||||||
<institutionid name="Schwab Retirement Plan Services" id="750"/>
|
|
||||||
<institutionid name="Scottrade Brokerage" id="808"/>
|
|
||||||
<institutionid name="Scottrade, Inc." id="623"/>
|
|
||||||
<institutionid name="Sears Card" id="715"/>
|
|
||||||
<institutionid name="Securities America" id="641"/>
|
|
||||||
<institutionid name="Security 1st FCU" id="601"/>
|
|
||||||
<institutionid name="ShareBuilder" id="614"/>
|
|
||||||
<institutionid name="Sierra Central Credit Union" id="502"/>
|
|
||||||
<institutionid name="Signal Financial Federal Credit Union" id="518"/>
|
|
||||||
<institutionid name="Silver State Schools CU" id="624"/>
|
|
||||||
<institutionid name="Siouxland Federal Credit Union" id="606"/>
|
|
||||||
<institutionid name="Smith Barney - Investments" id="625"/>
|
|
||||||
<institutionid name="Smith Barney - Transactions" id="464"/>
|
|
||||||
<institutionid name="Sound CU" id="793"/>
|
|
||||||
<institutionid name="South Carolina Bank and Trust" id="755"/>
|
|
||||||
<institutionid name="South Trust Bank" id="716"/>
|
|
||||||
<institutionid name="Southeastern CU" id="511"/>
|
|
||||||
<institutionid name="Southern Community Bank and Trust (SCB&T)" id="751"/>
|
|
||||||
<institutionid name="Southwest Airlines FCU" id="465"/>
|
|
||||||
<institutionid name="Southwest Missouri Bank" id="759"/>
|
|
||||||
<institutionid name="Spectrum Connect/Reich&Tang" id="463"/>
|
|
||||||
<institutionid name="St. Mary's Credit Union" id="815"/>
|
|
||||||
<institutionid name="Standard Federal Bank" id="717"/>
|
|
||||||
<institutionid name="Star One Credit Union" id="807"/>
|
|
||||||
<institutionid name="Sterne Agee" id="736"/>
|
|
||||||
<institutionid name="Summit Credit Union (WI)" id="547"/>
|
|
||||||
<institutionid name="Suncoast Credit Union" id="811"/>
|
|
||||||
<institutionid name="Suncoast Schools FCU" id="653"/>
|
|
||||||
<institutionid name="SunTrust" id="442"/>
|
|
||||||
<institutionid name="SVB" id="791"/>
|
|
||||||
<institutionid name="T. Rowe Price" id="466"/>
|
|
||||||
<institutionid name="Tangerine (Canada)" id="794"/>
|
|
||||||
<institutionid name="TD Ameritrade" id="425"/>
|
|
||||||
<institutionid name="TD Bank" id="652"/>
|
|
||||||
<institutionid name="Technology Credit Union" id="801"/>
|
|
||||||
<institutionid name="Technology Credit Union - CA" id="467"/>
|
|
||||||
<institutionid name="Texans Credit Union" id="771"/>
|
|
||||||
<institutionid name="Texas Dow Employees Credit Union" id="512"/>
|
|
||||||
<institutionid name="Texas State Bank - McAllen" id="586"/>
|
|
||||||
<institutionid name="The Community Bank" id="650"/>
|
|
||||||
<institutionid name="The Golden1 Credit Union" id="778"/>
|
|
||||||
<institutionid name="The Mechanics Bank" id="482"/>
|
|
||||||
<institutionid name="The Queen's Federal Credit Union" id="607"/>
|
|
||||||
<institutionid name="Think Federal Credit Union" id="538"/>
|
|
||||||
<institutionid name="Think Mutual Bank" id="812"/>
|
|
||||||
<institutionid name="TIAA-CREF" id="767"/>
|
|
||||||
<institutionid name="TIAA-CREF Retirement Services" id="797"/>
|
|
||||||
<institutionid name="Tower Federal Credit Union" id="769"/>
|
|
||||||
<institutionid name="Tri Boro Federal Credit Union" id="571"/>
|
|
||||||
<institutionid name="Truliant FCU" id="426"/>
|
|
||||||
<institutionid name="U.S. First FCU" id="582"/>
|
|
||||||
<institutionid name="UBS Financial Services Inc." id="459"/>
|
|
||||||
<institutionid name="UMB" id="660"/>
|
|
||||||
<institutionid name="UMB Bank" id="468"/>
|
|
||||||
<institutionid name="Umpqua Bank" id="725"/>
|
|
||||||
<institutionid name="Union Bank of California" id="469"/>
|
|
||||||
<institutionid name="United California Bank" id="718"/>
|
|
||||||
<institutionid name="United Federal CU - PowerLink" id="719"/>
|
|
||||||
<institutionid name="United Teletech Financial" id="470"/>
|
|
||||||
<institutionid name="UNIVERSITY & STATE EMPLOYEES CU" id="776"/>
|
|
||||||
<institutionid name="University Credit Union" id="560"/>
|
|
||||||
<institutionid name="University Federal Credit Union" id="513"/>
|
|
||||||
<institutionid name="US Bank" id="471"/>
|
|
||||||
<institutionid name="USAA Federal Savings Bank" id="483"/>
|
|
||||||
<institutionid name="USAA Investment Mgmt Co" id="665"/>
|
|
||||||
<institutionid name="Utah Community Credit Union" id="564"/>
|
|
||||||
<institutionid name="UW Credit Union" id="638"/>
|
|
||||||
<institutionid name="VALIC" id="720"/>
|
|
||||||
<institutionid name="Van Kampen Funds, Inc." id="721"/>
|
|
||||||
<institutionid name="Vanguard" id="799"/>
|
|
||||||
<institutionid name="Vanguard Group" id="722"/>
|
|
||||||
<institutionid name="Vanguard Group, The" id="479"/>
|
|
||||||
<institutionid name="Vantage Credit Union" id="499"/>
|
|
||||||
<institutionid name="Velocity Credit Union" id="723"/>
|
|
||||||
<institutionid name="Virginia Educators Credit Union" id="503"/>
|
|
||||||
<institutionid name="VISA Information Source" id="626"/>
|
|
||||||
<institutionid name="Voya" id="819"/>
|
|
||||||
<institutionid name="Wachovia Bank" id="537"/>
|
|
||||||
<institutionid name="Waddell & Reed - Ivy Funds" id="724"/>
|
|
||||||
<institutionid name="Weitz Funds" id="616"/>
|
|
||||||
<institutionid name="Wells Fargo" id="473"/>
|
|
||||||
<institutionid name="Wells Fargo Advantage Funds" id="591"/>
|
|
||||||
<institutionid name="Wells Fargo Advisor" id="516"/>
|
|
||||||
<institutionid name="Wells Fargo Advisors" id="737"/>
|
|
||||||
<institutionid name="Wells Fargo Bank" id="749"/>
|
|
||||||
<institutionid name="Wells Fargo Bank 2013" id="777"/>
|
|
||||||
<institutionid name="Wells Fargo Investments, LLC" id="568"/>
|
|
||||||
<institutionid name="Wells Fargo Trust-Investment Mgt" id="622"/>
|
|
||||||
<institutionid name="Windward Community FCU" id="604"/>
|
|
||||||
<institutionid name="Wings Financial" id="756"/>
|
|
||||||
<institutionid name="Woodsboro Bank" id="779"/>
|
|
||||||
<institutionid name="Wright Patman Congressional FCU" id="532"/>
|
|
||||||
<institutionid name="Wright Patt CU" id="800"/>
|
|
||||||
<institutionid name="WSECU" id="575"/>
|
|
||||||
<institutionid name="Yakima Valley Credit Union" id="514"/>
|
|
||||||
<institutionid name="Zions Bank" id="630"/>
|
|
||||||
<institutionid name="zWachovia" id="452"/>
|
|
||||||
</institutionlist>
|
|
|
@ -1 +0,0 @@
|
||||||
None
|
|
|
@ -1,8 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
import celery
|
import celery
|
||||||
import ofxparse
|
|
||||||
import requests.exceptions
|
|
||||||
|
|
||||||
import vanth.download
|
import vanth.download
|
||||||
import vanth.main
|
import vanth.main
|
||||||
|
@ -16,24 +14,13 @@ LOGGER = logging.getLogger(__name__)
|
||||||
app = celery.Celery('vanth')
|
app = celery.Celery('vanth')
|
||||||
app.conf.CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
|
app.conf.CELERY_ACCEPT_CONTENT = ['json', 'msgpack', 'yaml']
|
||||||
app.conf.CELERY_TASK_SERIALIZER = 'json'
|
app.conf.CELERY_TASK_SERIALIZER = 'json'
|
||||||
#app.conf.CELERY_ALWAYS_EAGER = True
|
app.conf.CELERY_ALWAYS_EAGER = True
|
||||||
|
|
||||||
@app.task()
|
@app.task()
|
||||||
def update_account(account_uuid):
|
def update_account(account_uuid):
|
||||||
LOGGER.debug("Updating account %s", account_uuid)
|
LOGGER.debug("Updating account %s", account_uuid)
|
||||||
account = vanth.platform.ofxaccount.by_uuid(account_uuid)
|
account = vanth.platform.ofxaccount.by_uuid(account_uuid)
|
||||||
source = vanth.platform.ofxsource.by_uuid(account['source']['uuid'])[0]
|
source = vanth.platform.ofxsource.by_uuid(account['source']['uuid'])[0]
|
||||||
try:
|
|
||||||
document = vanth.download.transactions(source, account)
|
document = vanth.download.transactions(source, account)
|
||||||
vanth.platform.ofxrecord.ensure_exists(account, document.body.statement.transactions.items)
|
vanth.platform.ofxrecord.ensure_exists(account, document.body.statement.transactions.items)
|
||||||
vanth.platform.ofxupdate.OFXUpdate.create(ofxaccount=account_uuid)
|
vanth.platform.ofxupdate.OFXUpdate.create(ofxaccount=account_uuid)
|
||||||
except requests.exceptions.ConnectionError as e:
|
|
||||||
LOGGER.error("Failed to download account data: %s", e)
|
|
||||||
|
|
||||||
@app.task()
|
|
||||||
def process_transaction_upload(account_uuid, filename):
|
|
||||||
LOGGER.debug("Processing file %s", filename)
|
|
||||||
account = vanth.platform.ofxaccount.by_uuid(account_uuid)
|
|
||||||
with open(filename, 'rb') as f:
|
|
||||||
document = ofxparse.OfxParser.parse(f)
|
|
||||||
vanth.platform.ofxrecord.ensure_exists(account, document.account.statement.transactions)
|
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
import celery
|
|
||||||
import celery.signals
|
|
||||||
|
|
||||||
import vanth.main
|
|
||||||
from vanth.celery import app
|
|
||||||
|
|
||||||
logging.basicConfig()
|
|
||||||
LOGGER = logging.getLogger(__name__)
|
|
||||||
|
|
||||||
config = vanth.main.get_config()
|
|
||||||
vanth.main.create_db_connection(config)
|
|
||||||
|
|
||||||
@celery.signals.setup_logging.connect
|
|
||||||
def on_logging(*args, **kwargs):
|
|
||||||
logging.getLogger().setLevel(logging.DEBUG)
|
|
||||||
LOGGER.info("Logging has been set up")
|
|
|
@ -1,7 +1,6 @@
|
||||||
import io
|
|
||||||
import ofxparse
|
|
||||||
import requests
|
import requests
|
||||||
|
|
||||||
|
import vanth.ofx
|
||||||
import vanth.platform.ofxaccount
|
import vanth.platform.ofxaccount
|
||||||
|
|
||||||
|
|
||||||
|
@ -13,8 +12,6 @@ def do_all():
|
||||||
|
|
||||||
def transactions(source, account):
|
def transactions(source, account):
|
||||||
body = vanth.ofx.query_transactions(source, account)
|
body = vanth.ofx.query_transactions(source, account)
|
||||||
url = 'https://ofx.americafirst.com/'
|
response = requests.post('https://ofx.americafirst.com/', data=body, headers={'Content-Type': 'application/x-ofx'})
|
||||||
response = requests.post(url, data=body, headers={'Content-Type': 'application/x-ofx'})
|
|
||||||
assert response.ok, response.text
|
assert response.ok, response.text
|
||||||
LOGGER.debug("Response from %s: %s %d bytes", url, response.status_code, len(response.text))
|
return vanth.ofx.parse(response.text)
|
||||||
return parse(response.text)
|
|
||||||
|
|
148
vanth/ofx.py
Normal file
148
vanth/ofx.py
Normal file
|
@ -0,0 +1,148 @@
|
||||||
|
import collections
|
||||||
|
import datetime
|
||||||
|
import re
|
||||||
|
|
||||||
|
import vanth.sgml
|
||||||
|
|
||||||
|
Document = collections.namedtuple('Document', ['header', 'body'])
|
||||||
|
|
||||||
|
class Body(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.status = Status(sgml['SIGNONMSGSRSV1']['SONRS']['STATUS'])
|
||||||
|
self.statement = TransactionStatement(sgml['BANKMSGSRSV1']['STMTTRNRS'])
|
||||||
|
|
||||||
|
class Status(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.code = sgml['CODE'].value
|
||||||
|
self.severity = sgml['SEVERITY'].value
|
||||||
|
self.message = sgml['MESSAGE'].value if sgml['MESSAGE'] else None
|
||||||
|
|
||||||
|
class TransactionStatement(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.trnuid = sgml['TRNUID'].value
|
||||||
|
self.status = Status(sgml['STATUS'])
|
||||||
|
self.transactions = TransactionList(sgml['STMTRS'])
|
||||||
|
|
||||||
|
class TransactionList(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.currency = sgml['CURDEF'].value
|
||||||
|
self.account = Account(sgml['BANKACCTFROM'])
|
||||||
|
self.start = _parse_date_with_tz(sgml['BANKTRANLIST']['DTSTART'].value)
|
||||||
|
self.end = _parse_date_with_tz(sgml['BANKTRANLIST']['DTEND'].value)
|
||||||
|
self.items = [Transaction(child) for child in sgml['BANKTRANLIST'].children if child.name == 'STMTTRN']
|
||||||
|
|
||||||
|
class Transaction(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.amount = float(sgml['TRNAMT'].value)
|
||||||
|
self.available = _parse_date(sgml['DTAVAIL'].value)
|
||||||
|
self.id = sgml['FITID'].value
|
||||||
|
self.memo = sgml['MEMO'].value
|
||||||
|
self.name = sgml['NAME'].value
|
||||||
|
self.posted = _parse_date(sgml['DTPOSTED'].value)
|
||||||
|
self.type = sgml['TRNTYPE'].value
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
return ((prop, getattr(self, prop)) for prop in ('amount', 'available', 'id', 'memo', 'name', 'posted', 'type'))
|
||||||
|
|
||||||
|
class Account(): # pylint:disable=too-few-public-methods
|
||||||
|
def __init__(self, sgml):
|
||||||
|
self.bankid = sgml['BANKID'].value
|
||||||
|
self.accountid = sgml['ACCTID'].value
|
||||||
|
self.type = sgml['ACCTTYPE'].value
|
||||||
|
|
||||||
|
def _fix_offset(offset):
|
||||||
|
result = int(offset) * 100
|
||||||
|
return "{:04d}".format(result) if result > 0 else "{:05d}".format(result)
|
||||||
|
|
||||||
|
def _parse_date(date):
|
||||||
|
return datetime.datetime.strptime(date, "%Y%m%d%H%M%S.000")
|
||||||
|
|
||||||
|
def _parse_date_with_tz(date):
|
||||||
|
match = re.match(r'(?P<datetime>\d+)\.\d+\[(?P<offset>[\d\-]+):(?P<tzname>\w+)\]', date)
|
||||||
|
if not match:
|
||||||
|
raise ValueError("Unable to extract datetime from {}".format(date))
|
||||||
|
formatted = "{datetime} {offset} {tzname}".format(
|
||||||
|
datetime = match.group('datetime'),
|
||||||
|
offset = _fix_offset(match.group('offset')),
|
||||||
|
tzname = match.group('tzname'),
|
||||||
|
)
|
||||||
|
return datetime.datetime.strptime(formatted, "%Y%m%d%H%M%S %z %Z")
|
||||||
|
|
||||||
|
def header():
|
||||||
|
return "\r\n".join([
|
||||||
|
"OFXHEADER:100",
|
||||||
|
"DATA:OFXSGML",
|
||||||
|
"VERSION:102",
|
||||||
|
"SECURITY:NONE",
|
||||||
|
"ENCODING:USASCII",
|
||||||
|
"CHARSET:1252",
|
||||||
|
"COMPRESSION:NONE",
|
||||||
|
"OLDFILEUID:NONE",
|
||||||
|
"NEWFILEUID:NONE",
|
||||||
|
])
|
||||||
|
|
||||||
|
def now():
|
||||||
|
return datetime.datetime.now().strftime("%Y%m%d%H%M%S.000[-7:MST]")
|
||||||
|
|
||||||
|
def signonmsg(institution, account):
|
||||||
|
return "\r\n".join([
|
||||||
|
"<SIGNONMSGSRQV1>",
|
||||||
|
"<SONRQ>",
|
||||||
|
"<DTCLIENT>{}".format(now()),
|
||||||
|
"<USERID>{}".format(account['user_id']),
|
||||||
|
"<USERPASS>{}".format(account['password']),
|
||||||
|
"<LANGUAGE>ENG",
|
||||||
|
"<FI>",
|
||||||
|
"<ORG>{}".format(institution['name']),
|
||||||
|
"<FID>{}".format(institution['fid']),
|
||||||
|
"</FI>",
|
||||||
|
"<APPID>QWIN",
|
||||||
|
"<APPVER>1200",
|
||||||
|
"</SONRQ>",
|
||||||
|
"</SIGNONMSGSRQV1>",
|
||||||
|
])
|
||||||
|
|
||||||
|
def bankmsg(institution, account, start):
|
||||||
|
return "\r\n".join([
|
||||||
|
"<BANKMSGSRQV1>",
|
||||||
|
"<STMTTRNRQ>",
|
||||||
|
"<TRNUID>00000000",
|
||||||
|
"<STMTRQ>",
|
||||||
|
"<BANKACCTFROM>",
|
||||||
|
"<BANKID>{}".format(institution['bankid']),
|
||||||
|
"<ACCTID>{}".format(account['account_id']),
|
||||||
|
"<ACCTTYPE>{}".format(account['type'].upper()),
|
||||||
|
"</BANKACCTFROM>",
|
||||||
|
"<INCTRAN>",
|
||||||
|
"<DTSTART>{}".format(start.strftime("%Y%m%d")),
|
||||||
|
"<INCLUDE>Y",
|
||||||
|
"</INCTRAN>",
|
||||||
|
"</STMTRQ>",
|
||||||
|
"</STMTTRNRQ>",
|
||||||
|
"</BANKMSGSRQV1>",
|
||||||
|
])
|
||||||
|
|
||||||
|
def body(institution, account, start):
|
||||||
|
return "<OFX>\r\n" + signonmsg(institution, account) + "\r\n" + bankmsg(institution, account, start) + "\r\n</OFX>"
|
||||||
|
|
||||||
|
def query_transactions(institution, account, start=None):
|
||||||
|
start = start or datetime.datetime.now() - datetime.timedelta(days=14)
|
||||||
|
return header() + (2*"\r\n") + body(institution, account, start) + "\r\n"
|
||||||
|
|
||||||
|
|
||||||
|
def _first_empty_line(lines):
|
||||||
|
for i, line in enumerate(lines):
|
||||||
|
if not line:
|
||||||
|
return i
|
||||||
|
|
||||||
|
def _parse_header(header_lines):
|
||||||
|
splits = [line.partition(':') for line in header_lines]
|
||||||
|
return {k: v for k, _, v in splits}
|
||||||
|
|
||||||
|
def parse(content):
|
||||||
|
lines = content.split('\r\n')
|
||||||
|
split = _first_empty_line(lines)
|
||||||
|
header_lines = lines[:split]
|
||||||
|
_header = _parse_header(header_lines)
|
||||||
|
_body = vanth.sgml.parse('\n'.join(lines[split+1:]))
|
||||||
|
return Document(_header, Body(_body))
|
|
@ -1,12 +0,0 @@
|
||||||
import xml.etree.ElementTree
|
|
||||||
|
|
||||||
|
|
||||||
def parse_child(element):
|
|
||||||
values = {child.tag: child.text for child in element}
|
|
||||||
values['id'] = element.attrib['id']
|
|
||||||
return values
|
|
||||||
|
|
||||||
def parse(data):
|
|
||||||
root = xml.etree.ElementTree.fromstring(data)
|
|
||||||
assert root.tag == 'institutions'
|
|
||||||
return [parse_child(element) for element in root]
|
|
|
@ -1,14 +1,10 @@
|
||||||
import logging
|
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
import werkzeug.utils
|
|
||||||
|
|
||||||
import vanth.celery
|
import vanth.celery
|
||||||
import vanth.pages.tools
|
import vanth.pages.tools
|
||||||
import vanth.platform.ofxaccount
|
import vanth.platform.ofxaccount
|
||||||
import vanth.platform.ofxsource
|
import vanth.platform.ofxsource
|
||||||
|
|
||||||
LOGGER = logging.getLogger()
|
|
||||||
blueprint = flask.Blueprint('accounts', __name__)
|
blueprint = flask.Blueprint('accounts', __name__)
|
||||||
|
|
||||||
@blueprint.route('/accounts/', methods=['GET'])
|
@blueprint.route('/accounts/', methods=['GET'])
|
||||||
|
@ -44,19 +40,3 @@ def post_account(arguments):
|
||||||
def post_update(arguments):
|
def post_update(arguments):
|
||||||
vanth.celery.update_account.delay(**arguments)
|
vanth.celery.update_account.delay(**arguments)
|
||||||
return flask.redirect('/accounts/')
|
return flask.redirect('/accounts/')
|
||||||
|
|
||||||
@blueprint.route('/transactions/', methods=['POST'])
|
|
||||||
@vanth.pages.tools.parse({
|
|
||||||
'account_uuid' : str,
|
|
||||||
})
|
|
||||||
def post_transactions(arguments):
|
|
||||||
if 'transactions' not in flask.request.files:
|
|
||||||
return flask.render_template('error.html', error="You did not include the content of the file in your upload")
|
|
||||||
transactions = flask.request.files['transactions']
|
|
||||||
if transactions.filename == '':
|
|
||||||
return flask.redirect('/accounts/{}/'.format(arguments['account_uuid']))
|
|
||||||
filename = werkzeug.utils.secure_filename(transactions.filename)
|
|
||||||
LOGGER.info("Saving uploaded file %s to %s", transactions.filename, filename)
|
|
||||||
transactions.save(filename)
|
|
||||||
vanth.celery.process_transaction_upload.delay(arguments['account_uuid'], filename)
|
|
||||||
return flask.redirect('/accounts/{}/'.format(arguments['account_uuid']))
|
|
||||||
|
|
|
@ -15,7 +15,7 @@ def parse(args):
|
||||||
if supplied is None:
|
if supplied is None:
|
||||||
missing_parameters.append(key)
|
missing_parameters.append(key)
|
||||||
else:
|
else:
|
||||||
values[key] = converter(supplied) if converter else supplied
|
values[key] = converter(supplied)
|
||||||
if missing_parameters:
|
if missing_parameters:
|
||||||
return (json.dumps({'errors': [{
|
return (json.dumps({'errors': [{
|
||||||
'title' : "Missing required paramter '{}'".format(parameter),
|
'title' : "Missing required paramter '{}'".format(parameter),
|
||||||
|
|
|
@ -30,8 +30,6 @@ def _select():
|
||||||
vanth.tables.OFXSource.c.uuid.label('source.uuid'),
|
vanth.tables.OFXSource.c.uuid.label('source.uuid'),
|
||||||
subselect,
|
subselect,
|
||||||
]).select_from(
|
]).select_from(
|
||||||
vanth.tables.OFXAccount.join(vanth.tables.OFXSource)
|
|
||||||
).select_from(
|
|
||||||
subselect
|
subselect
|
||||||
).where(
|
).where(
|
||||||
vanth.tables.OFXAccount.c.uuid == subselect.c.ofxaccount
|
vanth.tables.OFXAccount.c.uuid == subselect.c.ofxaccount
|
||||||
|
@ -65,12 +63,11 @@ def by_user(user_id):
|
||||||
query = query.where(
|
query = query.where(
|
||||||
vanth.tables.OFXAccount.c.owner == user_id
|
vanth.tables.OFXAccount.c.owner == user_id
|
||||||
)
|
)
|
||||||
LOGGER.debug(query)
|
|
||||||
return _execute_and_convert(query)
|
return _execute_and_convert(query)
|
||||||
|
|
||||||
def create(values):
|
def create(values):
|
||||||
engine = chryso.connection.get()
|
engine = chryso.connection.get()
|
||||||
values['type'] = values.pop('account_type')
|
|
||||||
values['source'] = sqlalchemy.select([
|
values['source'] = sqlalchemy.select([
|
||||||
vanth.tables.OFXSource.c.uuid
|
vanth.tables.OFXSource.c.uuid
|
||||||
]).where(vanth.tables.OFXSource.c.name == values.pop('institution'))
|
]).where(vanth.tables.OFXSource.c.name == values.pop('institution'))
|
||||||
|
|
|
@ -20,11 +20,12 @@ def ensure_exists(account, transactions):
|
||||||
LOGGER.debug("Have %d new transactions to save", len(new_records))
|
LOGGER.debug("Have %d new transactions to save", len(new_records))
|
||||||
to_insert = [{
|
to_insert = [{
|
||||||
'amount' : transaction.amount,
|
'amount' : transaction.amount,
|
||||||
|
'available' : transaction.available,
|
||||||
'fid' : transaction.id,
|
'fid' : transaction.id,
|
||||||
'memo' : transaction.memo,
|
'memo' : transaction.memo,
|
||||||
'name' : transaction.payee,
|
'name' : transaction.name,
|
||||||
'ofxaccount' : account['uuid'],
|
'ofxaccount' : account['uuid'],
|
||||||
'posted' : transaction.date,
|
'posted' : transaction.posted,
|
||||||
'type' : transaction.type,
|
'type' : transaction.type,
|
||||||
'uuid' : uuid.uuid4(),
|
'uuid' : uuid.uuid4(),
|
||||||
} for transaction in new_records]
|
} for transaction in new_records]
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import uuid
|
|
||||||
|
|
||||||
import chryso.connection
|
import chryso.connection
|
||||||
import sqlalchemy
|
|
||||||
|
|
||||||
import vanth.tables
|
import vanth.tables
|
||||||
|
|
||||||
|
@ -13,32 +11,10 @@ def _query_and_convert(query):
|
||||||
results = engine.execute(query).fetchall()
|
results = engine.execute(query).fetchall()
|
||||||
return [dict(result) for result in results]
|
return [dict(result) for result in results]
|
||||||
|
|
||||||
def by_uuid(uuid_):
|
def by_uuid(uuid):
|
||||||
query = vanth.tables.OFXSource.select().where(vanth.tables.OFXSource.c.uuid == uuid_)
|
query = vanth.tables.OFXSource.select().where(vanth.tables.OFXSource.c.uuid == uuid)
|
||||||
return _query_and_convert(query)
|
return _query_and_convert(query)
|
||||||
|
|
||||||
def get():
|
def get():
|
||||||
query = vanth.tables.OFXSource.select()
|
query = vanth.tables.OFXSource.select()
|
||||||
return _query_and_convert(query)
|
return _query_and_convert(query)
|
||||||
|
|
||||||
def ensure_exist(institutions):
|
|
||||||
engine = chryso.connection.get()
|
|
||||||
query = sqlalchemy.select([
|
|
||||||
vanth.tables.OFXSource.c.fid,
|
|
||||||
])
|
|
||||||
results = engine.execute(query).fetchall()
|
|
||||||
LOGGER.debug("Found %d OFX sources", len(results))
|
|
||||||
known_records = {result[vanth.tables.OFXSource.c.fid] for result in results}
|
|
||||||
new_records = [institution for institution in institutions if institution['fid'] not in known_records]
|
|
||||||
LOGGER.debug("Have %d new transactions to save", len(new_records))
|
|
||||||
to_insert = [{
|
|
||||||
'name' : institution['name'],
|
|
||||||
'fid' : institution['fid'],
|
|
||||||
'url' : institution['url'],
|
|
||||||
'uuid' : str(uuid.uuid4()),
|
|
||||||
} for institution in new_records]
|
|
||||||
if to_insert:
|
|
||||||
engine.execute(vanth.tables.OFXSource.insert(), to_insert) # pylint: disable=no-value-for-parameter
|
|
||||||
LOGGER.debug("Done inserting %d records", len(new_records))
|
|
||||||
else:
|
|
||||||
LOGGER.debug("Not performing insert, nothing to do")
|
|
||||||
|
|
65
vanth/sgml.py
Normal file
65
vanth/sgml.py
Normal file
|
@ -0,0 +1,65 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
class Node(): # pylint: disable=too-few-public-methods
|
||||||
|
def __init__(self, parent, name, children=None, value=None):
|
||||||
|
self.children = children or []
|
||||||
|
self.name = name
|
||||||
|
self.parent = parent
|
||||||
|
self.value = value
|
||||||
|
if parent:
|
||||||
|
parent.children.append(self)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
for child in self.children:
|
||||||
|
if child.name == key:
|
||||||
|
return child
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return "SGMLNode {} ({})".format(self.name, self.parent.name if self.parent else None)
|
||||||
|
|
||||||
|
def parse(content):
|
||||||
|
state = 'node-content'
|
||||||
|
buf = ''
|
||||||
|
parent_node = None
|
||||||
|
current_node = None
|
||||||
|
for c in content:
|
||||||
|
if c == '<':
|
||||||
|
if state == 'node-content':
|
||||||
|
if buf == '':
|
||||||
|
parent_node = current_node
|
||||||
|
LOGGER.debug("Node content was empty, setting parent node to %s", parent_node)
|
||||||
|
if current_node:
|
||||||
|
current_node.value = buf
|
||||||
|
LOGGER.debug("Set %s to %s", current_node.name, current_node.value)
|
||||||
|
buf = ''
|
||||||
|
state = 'node-name'
|
||||||
|
elif c == '>':
|
||||||
|
if state == 'node-name':
|
||||||
|
LOGGER.debug("Saw opening tag %s. With parent %s", buf, parent_node)
|
||||||
|
state = 'node-content'
|
||||||
|
current_node = Node(parent_node, buf)
|
||||||
|
buf = ''
|
||||||
|
elif state == 'closing-tag':
|
||||||
|
LOGGER.debug("Saw closing tag %s", buf)
|
||||||
|
state = 'closed-tag'
|
||||||
|
parent_node = current_node
|
||||||
|
while parent_node.parent and parent_node.name != buf:
|
||||||
|
parent_node = parent_node.parent
|
||||||
|
parent_node = parent_node.parent
|
||||||
|
buf = ''
|
||||||
|
LOGGER.debug("Set new parent to %s", parent_node.name if parent_node else None)
|
||||||
|
elif c == '/' and buf == '':
|
||||||
|
state = 'closing-tag'
|
||||||
|
parent_node = current_node.parent if current_node else None
|
||||||
|
else:
|
||||||
|
buf += c
|
||||||
|
root = current_node or parent_node
|
||||||
|
while root.parent:
|
||||||
|
root = root.parent
|
||||||
|
return root
|
||||||
|
|
||||||
|
def pformat(node, indent=0):
|
||||||
|
children = '\n'.join(pformat(child, indent+1) for child in node.children)
|
||||||
|
return "{}{}: {}{}".format('\t' * indent, node.name, node.value, "\n" + children if node.children else '')
|
|
@ -1,6 +1,6 @@
|
||||||
import chryso.constants
|
import chryso.constants
|
||||||
from sqlalchemy import (Column, Date, DateTime, ForeignKey, Integer,
|
from sqlalchemy import (Column, Date, DateTime, ForeignKey, Integer, MetaData,
|
||||||
Numeric, MetaData, String, Table, UniqueConstraint, func, text)
|
Numeric, String, Table, UniqueConstraint, func, text)
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
from sqlalchemy.dialects.postgresql import UUID
|
||||||
|
|
||||||
metadata = MetaData(naming_convention=chryso.constants.CONVENTION)
|
metadata = MetaData(naming_convention=chryso.constants.CONVENTION)
|
||||||
|
@ -75,46 +75,3 @@ OFXRecord = table('ofxrecord',
|
||||||
Column('posted', Date(), nullable=True), # The date the record posted
|
Column('posted', Date(), nullable=True), # The date the record posted
|
||||||
Column('type', String(255), nullable=True), # The type of the record, like 'POS'
|
Column('type', String(255), nullable=True), # The type of the record, like 'POS'
|
||||||
)
|
)
|
||||||
|
|
||||||
Pool = table('pool',
|
|
||||||
Column('balance', Integer(), nullable=False), # The balance of the pool, like -10 or +12
|
|
||||||
Column('name', String(255), nullable=False), # The name of the pool, like 'Bills' or 'Heating oil'
|
|
||||||
Column('parent', None, ForeignKey('pool.uuid', name='fk_parent'), nullable=True),
|
|
||||||
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
|
|
||||||
UniqueConstraint('name', 'user_uri', name='pool_name_by_user')
|
|
||||||
)
|
|
||||||
|
|
||||||
Spring = table('spring',
|
|
||||||
Column('description', String(1024), nullable=False), # A user-significant description
|
|
||||||
Column('name', String(256), nullable=False), # A user-significant name
|
|
||||||
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
|
|
||||||
UniqueConstraint('name', 'user_uri', name='sprint_name_by_user')
|
|
||||||
)
|
|
||||||
|
|
||||||
Sink = table('drain',
|
|
||||||
Column('description', String(1024), nullable=False), # A user-significant description
|
|
||||||
Column('name', String(256), nullable=False), # A user-significant name
|
|
||||||
Column('user_uri', String(2048), nullable=False), # The URI of the user that created the record
|
|
||||||
UniqueConstraint('name', 'user_uri', name='sink_name_by_user')
|
|
||||||
)
|
|
||||||
|
|
||||||
Flow = table('flow',
|
|
||||||
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
|
|
||||||
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
|
|
||||||
Column('destination', None, ForeignKey(Pool.c.uuid, name='fk_destination'), nullable=True), # The pool that receives the amount
|
|
||||||
Column('source', None, ForeignKey(Pool.c.uuid, name='fk_source'), nullable=True), # The pool that originally provides the amount
|
|
||||||
)
|
|
||||||
|
|
||||||
Inflow = table('inflow',
|
|
||||||
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
|
|
||||||
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
|
|
||||||
Column('destination', None, ForeignKey(Pool.c.uuid, name='fk_destination'), nullable=False), # The pool that receives the amount
|
|
||||||
Column('source', None, ForeignKey(Spring.c.uuid, name='fk_source'), nullable=False), # The sprint that originally provided the amount
|
|
||||||
)
|
|
||||||
|
|
||||||
Outflow = table('outflow',
|
|
||||||
Column('amount', Numeric(precision=20, scale=2, asdecimal=True), nullable=False), # The amount like -$177.91
|
|
||||||
Column('description', String(1024), nullable=False), # The description that is meaningful to the user
|
|
||||||
Column('destination', None, ForeignKey(Sink.c.uuid, name='fk_destination'), nullable=False), # The sink that receives the amount
|
|
||||||
Column('source', None, ForeignKey(Pool.c.uuid, name='fk_source'), nullable=False), # The pool that originally provided the amount
|
|
||||||
)
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue