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()