Initial commit for moneymanager output
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
This commit is contained in:
12
pytest.ini
Normal file
12
pytest.ini
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
[pytest]
|
||||||
|
env =
|
||||||
|
D:DB_HOST=''
|
||||||
|
D:DB_PORT=1
|
||||||
|
D:DB_NAME=''
|
||||||
|
|
||||||
|
mongodb_fixture_dir =
|
||||||
|
src/tests/mongo_fixtures
|
||||||
|
|
||||||
|
mongodb_fixtures =
|
||||||
|
imported_transactions
|
||||||
|
accounts
|
||||||
0
src/__init__.py
Normal file
0
src/__init__.py
Normal file
@@ -16,7 +16,7 @@ import time
|
|||||||
import watchdog.events
|
import watchdog.events
|
||||||
import watchdog.observers
|
import watchdog.observers
|
||||||
|
|
||||||
DATE_FORMAT = "%d/%m/%Y"
|
MB_DATE_FORMAT = "%d/%m/%Y"
|
||||||
|
|
||||||
if 'WATCH_DIR' in os.environ:
|
if 'WATCH_DIR' in os.environ:
|
||||||
WATCH_DIR = os.environ['WATCH_DIR']
|
WATCH_DIR = os.environ['WATCH_DIR']
|
||||||
@@ -34,20 +34,41 @@ MONGO_COL = 'imported_transactions'
|
|||||||
ACCOUNT_COL = 'accounts'
|
ACCOUNT_COL = 'accounts'
|
||||||
|
|
||||||
MONGO_URL = "mongodb://{}:{}".format(MONGO_URL, MONGO_PORT)
|
MONGO_URL = "mongodb://{}:{}".format(MONGO_URL, MONGO_PORT)
|
||||||
myclient = pymongo.MongoClient(MONGO_URL)
|
|
||||||
mydb = myclient[MONGO_DB]
|
|
||||||
mongo_col = mydb[MONGO_COL]
|
|
||||||
account_col = mydb[ACCOUNT_COL]
|
|
||||||
|
|
||||||
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
|
logging.basicConfig(format='%(asctime)s - %(message)s', level=logging.INFO)
|
||||||
logging.basicConfig(format='ERROR: %(asctime)s - %(message)s', level=logging.ERROR)
|
logging.basicConfig(format='ERROR: %(asctime)s - %(message)s', level=logging.ERROR)
|
||||||
|
|
||||||
class Handler(watchdog.events.PatternMatchingEventHandler):
|
class Handler(watchdog.events.PatternMatchingEventHandler):
|
||||||
def __init__(self):
|
mydb = None
|
||||||
|
|
||||||
|
def __init__(self, mongo_db=None):
|
||||||
# Set the patterns for PatternMatchingEventHandler
|
# Set the patterns for PatternMatchingEventHandler
|
||||||
watchdog.events.PatternMatchingEventHandler.__init__(self, patterns=['*.qfx'],
|
watchdog.events.PatternMatchingEventHandler.__init__(self, patterns=['*.qfx'],
|
||||||
ignore_directories=True, case_sensitive=False)
|
ignore_directories=True, case_sensitive=False)
|
||||||
|
|
||||||
|
if mongo_db is None:
|
||||||
|
myclient = pymongo.MongoClient(MONGO_URL)
|
||||||
|
mydb = myclient[MONGO_DB]
|
||||||
|
else:
|
||||||
|
self.mydb = mongo_db
|
||||||
|
|
||||||
|
#mongo_col = mydb[MONGO_COL]
|
||||||
|
#account_col = mydb[ACCOUNT_COL]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def line_to_moneybrilliant(line):
|
||||||
|
return {
|
||||||
|
'date': line['date'].strftime(MB_DATE_FORMAT),
|
||||||
|
'memo' : line['memo'],
|
||||||
|
'category': 'Uncategorised',
|
||||||
|
'amount': line['amount'],
|
||||||
|
'name': line['name']
|
||||||
|
}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def line_to_mmex(line):
|
||||||
|
return ''
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def write_csv(statement, out_file):
|
def write_csv(statement, out_file):
|
||||||
logging.info("Writing: " + out_file)
|
logging.info("Writing: " + out_file)
|
||||||
@@ -62,11 +83,27 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
|
|||||||
f.write("\r\n")
|
f.write("\r\n")
|
||||||
writer = DictWriter(f, fieldnames=fields)
|
writer = DictWriter(f, fieldnames=fields)
|
||||||
for line in statement:
|
for line in statement:
|
||||||
writer.writerow(line)
|
writer.writerow(Handler.line_to_moneybrilliant(line))
|
||||||
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def transaction_exists(line):
|
def write_mmex(statement, outfile):
|
||||||
existing_trans = mongo_col.find_one(line)
|
logging.info("Writing: " + out_file)
|
||||||
|
|
||||||
|
if len(statement) == 0:
|
||||||
|
logging.info("No transactions to write.")
|
||||||
|
return
|
||||||
|
|
||||||
|
fields = ['date', 'payee', 'amount', 'category', 'subcategory', 'number', 'notes']
|
||||||
|
with open(out_file, 'w') as f:
|
||||||
|
f.write("Date,Payee,Amount,Category,Sub Category,Number,Notes")
|
||||||
|
f.write("\r\n")
|
||||||
|
writer = DictWriter(f, fieldnames=fields)
|
||||||
|
for line in statement:
|
||||||
|
writer.writerow(line)
|
||||||
|
|
||||||
|
def transaction_exists(self, line):
|
||||||
|
existing_trans = self.mydb[MONGO_COL].find_one(line)
|
||||||
|
|
||||||
return existing_trans is not None
|
return existing_trans is not None
|
||||||
|
|
||||||
@@ -87,10 +124,9 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
|
|||||||
|
|
||||||
return dict_item
|
return dict_item
|
||||||
|
|
||||||
@staticmethod
|
def get_statement_from_qfx(self, qfx):
|
||||||
def get_statement_from_qfx(qfx):
|
|
||||||
|
|
||||||
account = account_col.find_one({"number": qfx.account.number})
|
account = self.mydb[ACCOUNT_COL].find_one({"number": qfx.account.number})
|
||||||
if account is None:
|
if account is None:
|
||||||
logging.error("No account for account number {} exists. Create one and re-process the file".format(qfx.account.number))
|
logging.error("No account for account number {} exists. Create one and re-process the file".format(qfx.account.number))
|
||||||
|
|
||||||
@@ -101,21 +137,25 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
line = {
|
line = {
|
||||||
'date': transaction.date.strftime(DATE_FORMAT),
|
'id': transaction.id,
|
||||||
|
'date': transaction.date,#.strftime(DATE_FORMAT),
|
||||||
'memo' : transaction.memo,
|
'memo' : transaction.memo,
|
||||||
'category': 'Uncategorised',
|
#'category': 'Uncategorised',
|
||||||
'amount': transaction.amount,
|
'amount': transaction.amount,
|
||||||
'name': account['name']
|
'name': account['name'],
|
||||||
|
'payee': transaction.payee,
|
||||||
|
'type': transaction.type
|
||||||
}
|
}
|
||||||
|
|
||||||
#mongo needs the decimal values in Decimal128, so create a version for it
|
|
||||||
line_d128 = Handler.convert_decimal(line.copy())
|
|
||||||
|
|
||||||
if Handler.transaction_exists(line_d128):
|
#mongo needs the decimal values in Decimal128, so create a version for it
|
||||||
|
line_d128 = self.convert_decimal(line.copy())
|
||||||
|
|
||||||
|
if self.transaction_exists(line_d128):
|
||||||
continue
|
continue
|
||||||
|
|
||||||
statement.append(line)
|
statement.append(line)
|
||||||
result = mongo_col.insert_one(line_d128)
|
result = self.mydb[MONGO_COL].insert_one(line_d128)
|
||||||
logging.info("New db entry stored: {}".format(result.inserted_id))
|
logging.info("New db entry stored: {}".format(result.inserted_id))
|
||||||
|
|
||||||
return statement, account['name']
|
return statement, account['name']
|
||||||
0
src/tests/__init__.py
Normal file
0
src/tests/__init__.py
Normal file
45
src/tests/test_converter.py
Normal file
45
src/tests/test_converter.py
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
from ..converter import Handler
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
from ofxparse import OfxParser
|
||||||
|
from datetime import datetime
|
||||||
|
from decimal import Decimal, getcontext
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
class TestConverter:
|
||||||
|
def test_get_statement_from_qfx(self, mongodb):
|
||||||
|
|
||||||
|
test_file = Path(os.getcwd()) / 'src' / 'tests' / 'sampleTrans1.qfx'
|
||||||
|
|
||||||
|
with open(test_file, 'r') as file:
|
||||||
|
qfx = OfxParser.parse(file, fail_fast=False)
|
||||||
|
|
||||||
|
handler = Handler(mongodb)
|
||||||
|
|
||||||
|
statement, account = handler.get_statement_from_qfx(qfx)
|
||||||
|
|
||||||
|
assert account == 'HSBC Everyday Global'
|
||||||
|
assert len(statement) == mongodb['imported_transactions'].find({}).count()
|
||||||
|
|
||||||
|
|
||||||
|
def test_line_to_moneybrilliant(self):
|
||||||
|
samples = [
|
||||||
|
{'id': '202201900001', 'date': '20220119', 'memo': 'GOLDEN PRAWN 15JAN22... GENERATED', 'amount': 1.04, 'name': 'HSBC Everyday Global', 'payee': '2% CASHBACK - ENJOY', 'type': 'credit'},
|
||||||
|
{'id': '202201900003', 'date': '20220119', 'memo': '19JAN22 ATMA896 08:2...843015 ATM', 'amount': -52.20, 'name': 'HSBC Everyday Global', 'payee': 'GOLDEN PRAWN', 'type': 'debit'},
|
||||||
|
{'id': '202201400001', 'date': '20220114', 'memo': '14JAN22 127196 18:3...892226 ATM', 'amount': -23.15, 'name': 'HSBC Everyday Global', 'payee': 'ALDI STORES - DARRA', 'type': 'debit'}
|
||||||
|
]
|
||||||
|
|
||||||
|
sample = samples[0]
|
||||||
|
sample['date'] = datetime.strptime(sample['date'], "%Y%m%d")
|
||||||
|
sample['amount'] = round(Decimal(sample['amount']), 2)
|
||||||
|
result = Handler.line_to_moneybrilliant(sample)
|
||||||
|
|
||||||
|
assert len(result) == 5
|
||||||
|
assert result['date'] == '19/01/2022'
|
||||||
|
assert result['memo'] == 'GOLDEN PRAWN 15JAN22... GENERATED'
|
||||||
|
assert result['category'] == 'Uncategorised'
|
||||||
|
assert result['amount'] == Decimal('1.04')
|
||||||
|
assert result['name'] == 'HSBC Everyday Global'
|
||||||
|
|
||||||
|
|
||||||
Reference in New Issue
Block a user