from flask import Flask, render_template, request, redirect import json from decimal import * from datetime import * import jinja2 from fpdf import FPDF, HTMLMixin import smtplib import os loader=jinja2.FileSystemLoader([os.path.join(os.path.dirname(__file__),"templates")]) environment = jinja2.Environment(loader=loader) from email.MIMEMultipart import MIMEMultipart from email.MIMEText import MIMEText from email.MIMEBase import MIMEBase from email import Encoders app = Flask(__name__) @app.route('/') def hello(): loans = [] loans.append( addLoan("1 Greenfield Lane Mortgage", "greenfield_mortgage.json" ) ) loans.append( addLoan("2 Bear Houses LLC Loan", "9Kloan.json" ) ) loans.append( addLoan("3 Brenda Mortgage", "brendamortgage.json" ) ) loans.append( addLoan("4 Dad Mortgage", "dadmortgage.json") ) loans.append( addLoan("5 Test Loan", "testloan.json")) if 'loan' in request.args: filename = request.args["loan"] else: return redirect('/?loan=' + loans[0]['filename']) loan = loadLoanInformation(getFullPathofLoanFile(filename)) amortizeLoan(loan) return render_template('main.html', filename=filename, loans=loans, model=loan) @app.route('/update_file', methods=['POST']) def update_file(): messages = [] loanFile = request.form["loan"] data = getDatastore(getFullPathofLoanFile(loanFile)) payment_history = data["payments"] if 'date' in request.form: if (request.form['date'] == ''): now = datetime.now() payment_date = str(now.strftime("%Y-%m-%d")) messages.append("No date was provided. Assuming today's date of " + payment_date + ".") else: payment_date = request.form['date'] messages.append("Date provided: " + payment_date + ".") else: now = datetime.now() payment_date = str(now.strftime("%Y-%m-%d")) payment_amount = Decimal('0.00') proceed_flag = True if 'amount' in request.form: if (request.form['amount'] != ''): try: payment_amount = Decimal(request.form['amount']) messages.append("Amount provided: " + str(payment_amount) + ".") except: payment_amount = Decimal('0.00') messages.append("The amount provided could not be interpreted. Your payment was not recorded.") proceed_flag = False else: pass if proceed_flag is True: try: backup_filename = loanFile + ".backup-" + datetime.now().strftime("%Y-%m-%d %H-%M-%S") backup_file = open(getFullPathofLoanFile(backup_filename), 'w+') json.dump(data, backup_file) backup_file.close() except: messages.append("A backup file could not be created. Your payment was not recorded.") proceed_flag = False else: messages.append("A backup of your file was created: '" + backup_filename +"'" ) if proceed_flag is True: try: payment_history.append( [payment_date, str(payment_amount)]) file = open(getFullPathofLoanFile(loanFile), 'w+') json.dump(data, file) file.close() except: messages.append("An error occurred writing to the file. Your payment file may be corrupt, " + \ "please consider rolling back to the backup created above.") else: messages.append("The payment was successfully written. ") return render_template('add.html', filename=loanFile, messages=messages) @app.route('/send_statement', methods=['POST']) def send_statement(): loanFile = request.form["loan"] subject = request.form["subject"] message = request.form["message"] loan = loadLoanInformation(getFullPathofLoanFile(loanFile)) amortizeLoan(loan) reportCreated = False textReport = pdfReport = htmlReport = None if 'text' in request.form: textReport = transformTemplate(selectTemplate('text'), loan) reportCreated = True if 'pdf' in request.form: pdfInterimReport = transformTemplate(selectTemplate('pdf'), loan) pdfReport = createPDF(pdfInterimReport) reportCreated = True if ('html' in request.form) or (reportCreated is False): htmlReport = transformTemplate(selectTemplate('html'), loan) # send email emailParameters = loan["email"] msg = generateEmail( emailParameters["from_address"], emailParameters["to_address"], subject, message, pdfReport, htmlReport, textReport) sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password']) return render_template('email.html', filename=loanFile) def addLoan(loanName, fileName): x = {} x['name'] = loanName x['filename'] = fileName return x def getFullPathofLoanFile(filename): return '/Users/john/PycharmProjects/mortgage/' + filename ################### # from old code # ################### def getStatementHeader(datastore): return datastore['header'] def getEmailInformation(datastore): return datastore['email'] def loadLoanInformation(filename): datastore = getDatastore(filename) loanModel = {} loanModel['datastore'] = datastore loanModel['email'] = getEmailInformation(datastore) loanModel['parameters'] = getLoanParameters(datastore) loanModel['lender'] = getLender(datastore) loanModel['borrower'] = getBorrower(datastore) loanModel['header'] = getStatementHeader(datastore) return loanModel def getLoanParameters(datastore): # read in the loan profile information loan = datastore['parameters'] annual_rate = Decimal(loan['interest_rate']) / 100 daily_interest_rate = annual_rate / 360 principal = Decimal(loan["principal"]).quantize(Decimal("1.00")) periods_per_year = Decimal(loan["periods_per_year"]) total_periods = Decimal(loan["periods"]) payment_day_of_month = int(loan['payment_day_of_month']) if "monthly_payment" in loan: monthly_payment = Decimal(loan["monthly_payment"]).quantize(Decimal("1.00")) else: # calculate expected monthly payment periodic_rate = annual_rate / periods_per_year discount_factor = (((1 + periodic_rate) ** total_periods) - 1) / ( periodic_rate * ((1 + periodic_rate) ** total_periods)) monthly_payment = (principal / discount_factor).quantize(Decimal("1.00")) loan['principal'] = principal # standardizes the format loan['annual_rate'] = annual_rate # standardizes the format loan['daily_interest_rate'] = daily_interest_rate loan['rate'] = '' + (annual_rate * 100).__str__() + '%' loan['next_payment_amt'] = 0 loan['next_payment_date'] = '12/12/12' loan['total_periods'] = total_periods loan['monthly_payment'] = monthly_payment datastore['parameters'] = loan return loan def getLender(datastore): return datastore['lender'] def getBorrower(datastore): return datastore['borrower'] def getDatastore(filename=None): try: if filename: with open(filename, 'r') as f: datastore = json.load(f) except Exception as e: print "An error occurred opening your loan file '%s'. " % filename print "The Exception:" print e.__repr__() quit() return datastore def amortizeLoan(loan): # loop over the payments and calculate the actual amortization monthly_payment = loan["parameters"]["monthly_payment"] actual_payments = loan["datastore"]["payments"] remaining_principal = loan["parameters"]["principal"] payment_day_of_month = int(loan["parameters"]["payment_day_of_month"]) daily_interest_rate = loan["parameters"]["daily_interest_rate"] total_periods = loan["parameters"]["total_periods"] interest_paid_through_date = datetime.strptime(loan["parameters"]["start_interest_date"], "%Y-%m-%d").date() next_payment_date = datetime.strptime(loan["parameters"]["first_payment_month"], '%Y-%m-%d').date() next_bill_date = date(year=next_payment_date.year, month=next_payment_date.month, day=payment_day_of_month) payment_number = 1 annual_interest = 0 total_interest = 0 old_bill_date = next_bill_date current_year = next_bill_date.year past_payments = [] future_payments = [] for payment in actual_payments: payment_date = datetime.strptime((payment[0]), '%Y-%m-%d').date() payment_amount = Decimal(payment[1]).quantize(Decimal("1.00")) days_since_last_payment = (payment_date - interest_paid_through_date).days #check for out of order payments, generally a sign of a data entry problem, especially years if days_since_last_payment < 0: print "Payment Number %s appears out of order. The payment date '%s' is before the previous payment on '%s'." \ % (payment_number, payment_date, interest_paid_through_date) quit() new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00")) new_principal = payment_amount - new_interest interest_paid_through_date = payment_date total_interest = total_interest + new_interest annual_interest = annual_interest + new_interest remaining_principal = remaining_principal - new_principal # create the payment record for the template to render payment_record = {} payment_record['year']=next_bill_date.year payment_record['month']=next_bill_date.month payment_record['payment_number'] = payment_number payment_record['bill_date'] = next_bill_date payment_record['payment_date'] = payment_date payment_record['days_of_interest'] = days_since_last_payment payment_record['payment_amount'] = payment_amount payment_record['principal_payment'] = payment_amount - new_interest payment_record['interest_payment'] = new_interest payment_record['new_balance'] = remaining_principal payment_record['interest_to_date'] = total_interest payment_record['annual_interest_to_date'] = annual_interest past_payments.append(payment_record) payment_number = payment_number + 1 old_bill_date = next_bill_date if old_bill_date.month < 12: next_bill_date = date(year=old_bill_date.year, month=old_bill_date.month + 1, day=payment_day_of_month) else: annual_interest = Decimal("0.00") next_bill_date = date(year=old_bill_date.year + 1, month=1, day = payment_day_of_month) loan["total_interest_paid_to_date"] = total_interest loan["parameters"]["next_due_date"] = next_bill_date if (remaining_principal < monthly_payment): loan["parameters"]["next_payment_amt"] = remaining_principal else: loan["parameters"]["next_payment_amt"] = monthly_payment # loop over remaining scheduled payments and present estimated amortization while (payment_number <= total_periods) and (remaining_principal > 0): days_since_last_payment = (next_bill_date - interest_paid_through_date).days new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00")) # make sure the last payment isn't too much if new_interest + remaining_principal < monthly_payment: monthly_payment = new_interest + remaining_principal new_principal = monthly_payment - new_interest remaining_principal = remaining_principal - new_principal interest_paid_through_date = next_bill_date # complete the future payment amortization record future_payment_record = {} future_payment_record['payment_number'] = payment_number future_payment_record['payment_date'] = next_bill_date future_payment_record['days_of_interest'] = days_since_last_payment future_payment_record['payment_amount'] = monthly_payment future_payment_record['principal_payment'] = new_principal future_payment_record['interest_payment'] = new_interest future_payment_record['new_balance'] = remaining_principal future_payments.append(future_payment_record) payment_number = payment_number + 1 old_bill_date = next_bill_date if old_bill_date.month < 12: next_bill_date = date(year=old_bill_date.year, month=old_bill_date.month + 1, day=payment_day_of_month) else: next_bill_date = date(year=old_bill_date.year + 1, month=1, day=payment_day_of_month) loan["balloon_payment"] = remaining_principal loan["past_payments"] = past_payments loan["future_payments"] = future_payments return def transformTemplate(template_fileName, loanModel): # template_filename = "statement.text.jinja" # setup jinja for creating the statement template = environment.get_template(template_fileName) print loanModel report = template.render(model=loanModel) return report def generateEmail(from_address, to_address, subject, body, pdf, html, txt): msg = MIMEMultipart() msg['Subject'] = subject msg['From'] = from_address msg['To'] = to_address msg.attach(MIMEText(body)) if (pdf != None): part = MIMEBase("application", "octet-stream") part.set_payload(pdf) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"') msg.attach(part) if (html != None): part = MIMEBase("text", "html") part.set_payload(html) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="statement.html"') msg.attach(part) if (txt != None): part = MIMEBase("text", "plain") part.set_payload(txt) Encoders.encode_base64(part) part.add_header('Content-Disposition', 'attachment; filename="statement.txt"') msg.attach(part) return msg def sendEmail(msg, from_address, to_address, passwd): try: server = smtplib.SMTP('smtp.gmail.com', 587) server.ehlo() server.starttls() server.login(from_address, passwd) server.sendmail(from_address, to_address, msg.as_string()) server.close() except: print "Couldn't send email." def createPDF(report): # create pdf class MyFPDF(FPDF, HTMLMixin): pass pdf = MyFPDF() pdf.set_font(family='Arial', size=12) pdf.add_page() pdf.write_html(report) # pdf.output(name='test.pdf', dest='F') return pdf.output(dest='S') def selectTemplate(format): if format == 'html': return 'statement.html.jinja' if format == 'pdf': return 'statement.pdf.jinja' if format == 'text': return 'statement.text.jinja' return 'statement.html.jinja' if __name__ == '__main__': app.debug = True app.run()