Files
2025-09-12 21:25:28 -04:00

175 lines
6.2 KiB
Python

import jinja2
import os
from flask import Flask, render_template, request
from decimal import *
from datetime import *
##############################################################################
#
# This is the BQL query to put into Fava to generate the CSV that can be used
# to create the JSON file for this. Might consider reading in the CSV directly
# and prompting for the other information?
#pip
# select date, number where year=2022 And account~".*:CreditLine"
#
##############################################################################
#
# Consider altering this program to generate the text to go into the beancount
# files directly so that it can just be a cut and paste job.
#
##############################################################################
app = Flask(__name__)
trans_template = '''{0} * "Interest Due on LoC {1} - {2}"\n\t{3} {4} USD\n\t{5}\n\n'''
loader = jinja2.FileSystemLoader(os.path.dirname(__file__))
environment = jinja2.Environment(loader=loader)
DZERO = Decimal("0.00")
######
# Flask Call Backs
######
@app.route('/', methods=['GET', 'POST'])
def hello():
now = datetime.now()
model = dict()
model['year'] = now.date().strftime("%Y")
model['rate'] = 8
model['starting_balance'] = 0
model['principal_account'] = "Liabilities:Current:CreditLine"
model['interest_account'] = "Expenses:Interest:LoCInterest"
model['int_txns'] = 'Not calculated.'
model['txns'] = []
model['net_balance_changes'] = {}
model['transcript'] = []
if request.method == 'POST':
model['year'] = request.form['year']
model['rate'] = request.form['rate']
starting_balance = Decimal(request.form['starting_balance'])
model['starting_balance'] = (DZERO + starting_balance).quantize(Decimal("0.00"))
model['principal_account'] = request.form['principal_account']
model['interest_account'] = request.form['interest_account']
files = request.files
if 'transactions' in files:
result, model['message'], txns = validateUserInput(model, files['transactions'])
if result is True:
model['txns'] = txns
model['int_txns'] = generate_interest_postings(model, txns)
return render_template('main.html', model=model)
def validateUserInput(model: object, csv_content: object) -> (bool, str, dict):
#TODO: What validations should I put here?
#put the contents of the csv file into a list
transactions = {}
for line in csv_content:
text = line.decode('utf-8').rstrip()
if "date" in text:
pass
else:
items = text.split(',')
current_day = items[0]
amount = Decimal("0.00") + Decimal(items[1])
if current_day in transactions:
transactions[current_day].append(amount)
else:
transactions[current_day] = [amount]
return True, '', transactions
def appendToTranscript(transcript: dict, date: str, type: str, amount: Decimal, balance: Decimal):
transcript.append({"date": date, "type": type, "amount": amount, "balance": balance})
def generate_interest_postings(model: dict, transactions: dict) -> list:
postings = []
transcript = []
starting_balance = Decimal(model['starting_balance'])
apr = Decimal(model['rate']) / 100
daily_interest_rate = apr / 360
year = int(model['year'])
first_day = datetime(year, 1, 1)
last_day = datetime(year, 12, 31)
days_in_year = (last_day - first_day).days + 2
current_balance = starting_balance
accummulated_interest = DZERO
daily_interest = calculateDailyInterest(current_balance, daily_interest_rate)
for i in range(1, days_in_year):
current_date = (first_day + timedelta(days=i-1)).date()
if current_date.day == 1:
appendToTranscript(transcript, current_date, "Balance", "", current_balance)
#first add this day's transactions to the current balance
if current_date.__str__() in transactions:
for item in transactions[current_date.__str__()]:
current_balance = current_balance - item
appendToTranscript(transcript, current_date, "Transaction", item, current_balance)
daily_interest = calculateDailyInterest(current_balance, daily_interest_rate)
appendToTranscript(transcript, current_date, "Daily Interest Change", daily_interest, "")
#accumulate this day's interest
accummulated_interest = accummulated_interest + daily_interest
#if this is the last day of a month, accrue the accumulated interest to the balance
#then recalculate the new daily_interest accrual amount
#then emit that month's interest accrual posting
#then zero accumulated interest
if is_last_day_of_month(current_date):
current_balance = current_balance + accummulated_interest
daily_interest = calculateDailyInterest(current_balance, daily_interest_rate)
postings.append(trans_template.format(fava_date_format(current_date),
first_day_of_month(current_date), current_date,
model['principal_account'], accummulated_interest * -1,
model['interest_account']))
appendToTranscript(transcript, current_date, "Interest Posting", accummulated_interest * -1, current_balance)
accummulated_interest = DZERO
model['transcript'] = transcript
return postings
def is_last_day_of_month(some_date):
if some_date.day < 28:
return False
next_day = some_date + timedelta(days=1)
if next_day.month == some_date.month:
return False
return True
def first_day_of_month(some_date):
firstDayOfMonth = date(some_date.year, some_date.month, 1)
return firstDayOfMonth
def calculateDailyInterest(principal, rate):
return (principal*rate).quantize(Decimal('.01'))
def fava_date_format(some_date):
day = some_date.day
month = some_date.month
year = some_date.year
return "{0}-{1}-{2}".format(year, month, day)
def main():
app.debug = True
app.run(host='0.0.0.0', port=8000)
if __name__ == '__main__':
main()