Save the processed transactions so they don't get processed a second time

This commit is contained in:
2022-01-12 20:34:14 +10:00
parent a4e43522db
commit f694a67b2d
3 changed files with 76 additions and 12 deletions

3
.gitignore vendored
View File

@@ -139,3 +139,6 @@ dmypy.json
cython_debug/ cython_debug/
.devcontainer/devcontainer.json .devcontainer/devcontainer.json
.vscode/launch.json
dev.env
.vscode/launch.json

View File

@@ -2,10 +2,17 @@ FROM python:3.8
RUN pip install ofxparse RUN pip install ofxparse
RUN pip install watchdog RUN pip install watchdog
RUN pip install pymongo
RUN useradd --uid 99 --gid 100 hsbc
RUN useradd --uid 99 --gid 100 -m hsbc
USER hsbc USER hsbc
# Creates a non-root user and adds permission to access the /app folder
# For more info, please refer to https://aka.ms/vscode-docker-python-configure-containers
#RUN useradd -u 1000 -m ubuntu && chown -R ubuntu /home/ubuntu
#USER ubuntu
ADD converter.py /app/ ADD converter.py /app/
CMD [ "python", "./app/converter.py" ] CMD [ "python", "./app/converter.py" ]

View File

@@ -6,17 +6,36 @@ from glob import glob
from ofxparse import OfxParser from ofxparse import OfxParser
from pathlib import Path from pathlib import Path
from decimal import Decimal
from bson.decimal128 import Decimal128
import os import os
import pymongo
import time import time
import watchdog.events import watchdog.events
import watchdog.observers import watchdog.observers
DATE_FORMAT = "%d/%m/%Y" DATE_FORMAT = "%d/%m/%Y"
WATCH_DIR = '/mnt/data/'
if 'WATCH_DIR' in os.environ:
WATCH_DIR = os.environ['WATCH_DIR']
else:
WATCH_DIR = '/mnt/data/'
PATTERN = '*.qfx' PATTERN = '*.qfx'
BACKUP_DIR = 'Imported' BACKUP_DIR = 'Imported'
CONVERTED_DIR = 'Converted' CONVERTED_DIR = 'Converted'
MONGO_URL = os.environ['DB_HOST']
MONGO_PORT = os.environ['DB_PORT']
MONGO_DB = os.environ['DB_NAME']
MONGO_COL = os.environ['DB_COL']
MONGO_URL = "mongodb://{}:{}".format(MONGO_URL, MONGO_PORT)
myclient = pymongo.MongoClient(MONGO_URL)
mydb = myclient[MONGO_DB]
mongo_col = mydb[MONGO_COL]
class Handler(watchdog.events.PatternMatchingEventHandler): class Handler(watchdog.events.PatternMatchingEventHandler):
def __init__(self): def __init__(self):
@@ -27,6 +46,11 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
@staticmethod @staticmethod
def write_csv(statement, out_file): def write_csv(statement, out_file):
print("Writing: " + out_file) print("Writing: " + out_file)
if len(statement) == 0:
print("No transactions to write.")
return
fields = ['date', 'memo', 'category', 'amount', 'name'] fields = ['date', 'memo', 'category', 'amount', 'name']
with open(out_file, 'w') as f: with open(out_file, 'w') as f:
f.write("Date,Original Description,Category,Amount,Account Name") f.write("Date,Original Description,Category,Amount,Account Name")
@@ -35,16 +59,34 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
for line in statement: for line in statement:
writer.writerow(line) writer.writerow(line)
@staticmethod
def transaction_exists(line):
existing_trans = mongo_col.find_one(line)
return existing_trans is not None
@staticmethod
def convert_decimal(dict_item):
# This function iterates a dictionary looking for types of Decimal and converts them to Decimal128
# Embedded dictionaries and lists are called recursively.
if dict_item is None: return None
for k, v in list(dict_item.items()):
if isinstance(v, dict):
Handler.convert_decimal(v)
elif isinstance(v, list):
for l in v:
Handler.convert_decimal(l)
elif isinstance(v, Decimal):
dict_item[k] = Decimal128(str(v))
return dict_item
@staticmethod @staticmethod
def get_statement_from_qfx(qfx): def get_statement_from_qfx(qfx):
balance = qfx.account.statement.balance
statement = [] statement = []
credit_transactions = ['credit', 'dep', 'int']
debit_transactions = ['debit', 'atm', 'pos', 'xfer', 'check']
for transaction in qfx.account.statement.transactions: for transaction in qfx.account.statement.transactions:
amount = ""
balance = balance + transaction.amount
if transaction.payee.startswith("PENDING:"): if transaction.payee.startswith("PENDING:"):
continue continue
@@ -56,7 +98,17 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
'amount': transaction.amount, 'amount': transaction.amount,
'name': 'HSBC Everyday Global' 'name': 'HSBC Everyday Global'
} }
#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):
continue
statement.append(line) statement.append(line)
result = mongo_col.insert_one(line_d128)
print("New db entry stored: {}".format(result.inserted_id))
return statement return statement
@staticmethod @staticmethod
@@ -84,10 +136,10 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
historicalSize = -1 historicalSize = -1
while (historicalSize != os.path.getsize(event.src_path)): while (historicalSize != os.path.getsize(event.src_path)):
historicalSize = os.path.getsize(event.src_path) historicalSize = os.path.getsize(event.src_path)
print('waiting....') print('waiting for copy to finish....')
time.sleep(1) time.sleep(1)
print("file copy has now finished") print("file copy has now finished")
with open(event.src_path, 'r') as file: with open(event.src_path, 'r') as file:
@@ -115,6 +167,7 @@ class Handler(watchdog.events.PatternMatchingEventHandler):
if not destination.exists(): if not destination.exists():
path.replace(destination) path.replace(destination)
print("Processing successfully finished for {}".format(event.src_path))
if __name__ == "__main__": if __name__ == "__main__":
event_handler = Handler() event_handler = Handler()
@@ -122,6 +175,7 @@ if __name__ == "__main__":
observer.schedule(event_handler, path=WATCH_DIR, recursive=False) observer.schedule(event_handler, path=WATCH_DIR, recursive=False)
observer.start() observer.start()
print('Converter running, waiting for files in {}'.format(WATCH_DIR))
try: try:
while True: while True:
time.sleep(1) time.sleep(1)