:G: Enter commit message. Lines beginning with 'HG:' are removed.

This commit is contained in:
john
2022-10-03 00:02:12 -04:00
parent 3ccd81cf76
commit 466d2dce8b
20 changed files with 1630 additions and 729 deletions

View File

@@ -1,3 +1,5 @@
./venv/
.hgignore
.Python
.DS_Store
./bin/
./lib/
./.idea/

View File

@@ -23,7 +23,7 @@
"date": "Today",
"title": "Installment Loan Test Statement"
},
"payments": [
"payments": [
[
"2019-07-15",
"475.00"
@@ -52,6 +52,36 @@
"2020-01-15",
"0",
"50"
],
[
"2020-09-27",
"278.15",
"20"
],
[
"2020-09-29",
"296.12",
"25"
],
[
"2020-10-16",
"133",
"0.00"
],
[
"2020-10-16",
"133",
"0.00"
],
[
"2020-10-16",
"133",
"0.00"
],
[
"2021-05-03",
"1",
"1"
]
],
"borrower": {
@@ -67,6 +97,6 @@
"subject": "Your test loan statement...",
"body": "Your test loan statement is attached.",
"server": "smtp.gmail.com",
"password": "pvyrbcnzrjoizprn"
"password": "builcuouzobxroow"
}
}

View File

@@ -1,69 +0,0 @@
{
"htmlTemplate": "statement.pdf.jinja",
"txtTemplate": "statement.txt.jinja",
"format": "html",
"email": {
"from_address": "jkent3rd@gmail.com",
"to_address": "grady@gradystreet.com",
"server": "smtp.gmail.com",
"password": "pvyrbcnzrjoizprn",
"template": "./template.txt",
"send_pdf": "true",
"send_text": "true",
"subject": "Your loan statement...",
"body": "Your loan statement is attached.",
"text": "Please see your most recent account statement."
},
"parameters": {
"account_number": "100100",
"principal": 10020.00,
"interest_rate": 6.5,
"periods_per_year": 12,
"periods": 18,
"start_date": "2017-12-05",
"start_interest_date": "2017-12-05",
"first_payment_month": "2018-01-15",
"monthly_payment": 589.00,
"payment_day_of_month": "15"
},
"payments": [
["2018-01-16", "589.00"],
["2018-03-30", "589.00"],
["2018-04-06", "589.00"],
["2018-04-12", "589.00"],
["2018-05-28", "589.00"],
["2018-06-12", "589.00"],
["2018-07-13", "589.00"],
["2018-08-14", "589.00"],
["2018-09-26", "589.00"],
["2018-10-15", "0"],
["2018-11-29", "589.00"],
["2018-12-31", "589.00"],
["2019-01-15", "0.00"],
["2019-02-13", "589.00"],
["2019-03-15", "0"],
["2019-04-15", "0"],
["2019-05-15", "589.00"],
["2019-06-21", "2985.00"]
],
"borrower": {
"name": "Bear Houses, LLC",
"address": "301 N Beauregard St Apt 203",
"city": "Alexandria",
"state": "VA",
"zip": "22312"
},
"lender": {
"name": "John Kent",
"phone": "703.343.0782",
"address": "743 Madison St NW",
"city": "Washington",
"state": "DC",
"zip": "20011"
},
"header": {
"title": "Installment Loan Statement",
"date": "Today"
}
}

View File

@@ -22,7 +22,7 @@
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "pvyrbcnzrjoizprn",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
@@ -129,14 +129,164 @@
[
"2020-06-01",
"278.15"
],
[
"2020-07-01",
"278.15"
],
[
"2020-08-01",
"278.15"
],
[
"2020-09-01",
"278.15"
],
[
"2020-10-01",
"278.15"
],
[
"2020-11-01",
"278.15",
"0.00"
],
[
"2020-12-01",
"278.15",
"0.00"
],
[
"2021-01-01",
"278.15",
"0"
],
[
"2021-02-01",
"278.15",
"0"
],
[
"2021-03-01",
"278.15",
"0.00"
],
[
"2021-04-01",
"278.15",
"0.00"
],
[
"2021-04-30",
"278.15",
"0"
],
[
"2021-06-01",
"278.15",
"0.00"
],
[
"2021-07-01",
"278.15",
"0"
],
[
"2021-07-31",
"278.15",
"0"
],
[
"2021-09-01",
"278.15",
"0.00",
"False"
],
[
"2021-10-01",
"278.15",
"0.00",
"False"
],
[
"2021-11-01",
"278.15",
"0",
"False"
],
[
"2021-12-01",
"278.15",
"0.00",
"False"
],
[
"2022-01-01",
"278.15",
"0.00",
"False"
],
[
"2022-02-01",
"278.15",
"0.00",
"False"
],
[
"2022-03-01",
"278.15",
"0.00",
"False"
],
[
"2022-04-01",
"278.15",
"0.00",
"False"
],
[
"2022-05-01",
"278.15",
"0.00",
"False"
],
[
"2022-06-01",
"278.15",
"0.00",
"False"
],
[
"2022-07-01",
"278.15",
"0.00",
"False"
],
[
"2022-08-01",
"278.15",
"0.00",
"False"
],
[
"2022-09-01",
"278.15",
"0.00",
"False"
],
[
"2022-10-01",
"278.15",
"0.00",
"False"
]
],
"borrower": {
"city": "Washington",
"state": "DC",
"name": "Grandma Tina's Properties, LLC",
"zip": "20008",
"address": "3100 Connecticut Ave NW Apt 144"
"zip": "20001",
"address": "1720 New Jersey Ave NW Unit 401"
},
"txtTemplate": "statement.txt.jinja"
}

View File

@@ -22,7 +22,7 @@
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "pvyrbcnzrjoizprn",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
@@ -129,14 +129,164 @@
[
"2020-06-01",
"278.15"
],
[
"2020-07-01",
"278.15"
],
[
"2020-08-01",
"278.15"
],
[
"2020-09-01",
"278.15"
],
[
"2020-10-01",
"278.15"
],
[
"2020-11-01",
"278.15",
"0.00"
],
[
"2020-12-01",
"278.15",
"0.00"
],
[
"2021-01-01",
"278.15",
"0"
],
[
"2021-02-01",
"278.15",
"0"
],
[
"2021-03-01",
"278.15",
"0.00"
],
[
"2021-04-01",
"278.15",
"0.00"
],
[
"2021-04-30",
"278.15",
"0"
],
[
"2021-06-01",
"278.15",
"0.00"
],
[
"2021-07-01",
"278.15",
"0"
],
[
"2021-07-31",
"278.15",
"0"
],
[
"2021-09-01",
"278.15",
"0.00",
"False"
],
[
"2021-10-01",
"278.15",
"0.00",
"False"
],
[
"2021-11-01",
"278.15",
"0",
"False"
],
[
"2021-12-01",
"278.15",
"0.00",
"False"
],
[
"2022-01-01",
"278.15",
"0.00",
"False"
],
[
"2022-02-01",
"278.15",
"0",
"False"
],
[
"2022-03-01",
"278.15",
"0.00",
"False"
],
[
"2022-04-01",
"278.15",
"0.00",
"False"
],
[
"2022-05-01",
"278.15",
"0.00",
"False"
],
[
"2022-06-01",
"278.15",
"0.00",
"False"
],
[
"2022-07-01",
"278.15",
"0.00",
"False"
],
[
"2022-08-01",
"278.15",
"0.00",
"False"
],
[
"2022-09-01",
"278.15",
"0.00",
"False"
],
[
"2022-10-01",
"278.15",
"0.00",
"False"
]
],
"borrower": {
"city": "Washington",
"state": "DC",
"name": "Grandma Tina's Properties, LLC",
"zip": "20008",
"address": "3100 Connecticut Ave NW Apt 144"
"zip": "20001",
"address": "1720 New Jersey Ave NW Unit 401"
},
"txtTemplate": "statement.txt.jinja"
}

View File

@@ -1,16 +1,16 @@
{
"htmlTemplate": "statement.pdf.jinja",
"parameters": {
"monthly_payment": 475.0,
"interest_rate": 6.5,
"start_interest_date": "2019-06-17",
"monthly_payment": 842.93,
"interest_rate": 5.75,
"start_interest_date": "2022-08-12",
"payment_day_of_month": "15",
"first_payment_month": "2019-07-15",
"account_number": "100002",
"periods": 20,
"start_date": "2019-06-15",
"first_payment_month": "2022-09-15",
"account_number": "22-0001",
"periods": 180,
"start_date": "2022-08-12",
"periods_per_year": 12,
"principal": 9000.0
"principal": 101508.04
},
"format": "html",
"email": {
@@ -22,66 +22,33 @@
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "pvyrbcnzrjoizprn",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
"city": "Washington",
"name": "Rivanna Graphite Investments, LLC",
"zip": "20008",
"zip": "20001",
"phone": "703.343.0782",
"state": "DC",
"address": "3100 Connecticut Ave NW Apt 144"
"address": "1720 New Jersey Ave NW Unit 401"
},
"header": {
"date": "Today",
"title": "Installment Loan Statement"
"title": "Mortgage Loan Statement - Harbor Bend Loan 1"
},
"payments": [
[
"2019-07-15",
"475.00"
],
[
"2019-08-14",
"475.00"
],
[
"2019-09-17",
"475"
],
[
"2019-10-15",
"0"
],
[
"2019-11-15",
"0"
],
[
"2019-12-5",
"475"
],
[
"2020-01-15",
"2022-08-15",
"803",
"0",
"0"
"False"
],
[
"2020-02-17",
"0"
],
[
"2020-03-03",
"475"
],
[
"2020-04-15",
"0"
],
[
"2020-05-19",
"475"
"2022-09-09",
"803",
"0.00",
"False"
]
],
"borrower": {

View File

@@ -22,16 +22,16 @@
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "pvyrbcnzrjoizprn",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
"city": "Washington",
"name": "Rivanna Graphite Investments, LLC",
"zip": "20008",
"zip": "20001",
"phone": "703.343.0782",
"state": "DC",
"address": "3100 Connecticut Ave NW Apt 144"
"address": "1720 New Jersey Ave NW Unit 401"
},
"header": {
"date": "Today",
@@ -162,6 +162,86 @@
[
"2020-06-16",
"803.00"
],
[
"2020-07-15",
"803"
],
[
"2020-08-13",
"803"
],
[
"2020-09-17",
"803"
],
[
"2020-10-15",
"803",
"0.00"
],
[
"2020-11-16",
"803.00",
"0"
],
[
"2020-12-15",
"803.00",
"0.00"
],
[
"2021-01-13",
"803.00",
"0"
],
[
"2021-02-16",
"803.00",
"0"
],
[
"2021-03-22",
"0",
"32.12"
],
[
"2021-04-14",
"803.00",
"0.00"
],
[
"2021-05-18",
"803.00",
"0"
],
[
"2021-06-15",
"803.00",
"0"
],
[
"2021-07-31",
"0",
"32.12"
],
[
"2021-08-18",
"803",
"0.00",
"False"
],
[
"2021-09-24",
"803",
"0.00",
"False"
],
[
"2021-09-25",
"0",
"0.00",
"False"
]
],
"borrower": {

View File

@@ -0,0 +1,128 @@
{
"htmlTemplate": "statement.pdf.jinja",
"parameters": {
"monthly_payment": 831.0,
"interest_rate": 5.5,
"start_interest_date": "2021-10-13",
"payment_day_of_month": "15",
"first_payment_month": "2021-11-15",
"account_number": "21-0004",
"periods": 180,
"start_date": "2021-10-13",
"periods_per_year": 12,
"principal": 101123.26
},
"format": "html",
"email": {
"body": "Your loan statement is attached.",
"to_address": "grady@gradystreet.com",
"from_address": "jkent3rd@gmail.com",
"text": "Please see your most recent account statement.",
"server": "smtp.gmail.com",
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
"city": "Washington",
"name": "Rivanna Graphite Investments, LLC",
"zip": "20001",
"phone": "703.343.0782",
"state": "DC",
"address": "1720 New Jersey Ave NW Unit 401"
},
"header": {
"date": "Today",
"title": "Mortgage Loan Statement - 195 Greenfield Lane, Pearl MS"
},
"payments": [
[
"2021-10-14",
"803",
"0.00",
"False"
],
[
"2021-10-14",
"28",
"0.00",
"False"
],
[
"2021-10-18",
"28",
"0.00",
"False"
],
[
"2021-11-16",
"803.00",
"0.00",
"False"
],
[
"2021-11-19",
"28",
"0.00",
"False"
],
[
"2022-02-10",
"831",
"0.00",
"False"
],
[
"2022-04-04",
"1000",
"55.00",
"False"
],
[
"2022-05-15",
"0",
"0.00",
"False"
],
[
"2022-06-15",
"0",
"0.00",
"False"
],
[
"2022-07-15",
"0",
"0.00",
"False"
],
[
"2022-07-26",
"803",
"0.00",
"False"
],
[
"2022-08-12",
"0",
"0.00",
"False"
],
[
"2022-08-12",
"101508.04",
"0.00",
"False"
]
],
"borrower": {
"city": "Alexandria",
"state": "VA",
"name": "Bear Houses, LLC",
"zip": "22312",
"address": "301 N Beauregard St Apt 203"
},
"txtTemplate": "statement.txt.jinja"
}

View File

@@ -0,0 +1,50 @@
{
"htmlTemplate": "statement.pdf.jinja",
"parameters": {
"monthly_payment": 848,
"interest_rate": 5.75,
"start_interest_date": "2022-08-12",
"payment_day_of_month": "15",
"first_payment_month": "2022-09-15",
"account_number": "22-0001",
"periods": 180,
"start_date": "2022-08-12",
"periods_per_year": 12,
"principal": 101508.04
},
"format": "html",
"email": {
"body": "Your loan statement is attached.",
"to_address": "grady@gradystreet.com",
"from_address": "jkent3rd@gmail.com",
"text": "Please see your most recent account statement.",
"server": "smtp.gmail.com",
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
"city": "Washington",
"name": "Rivanna Graphite Investments, LLC",
"zip": "20001",
"phone": "703.343.0782",
"state": "DC",
"address": "1720 New Jersey Ave NW Unit 401"
},
"header": {
"date": "Today",
"title": "Mortgage Loan Statement - Harbor Bend Loan 1"
},
"payments": [
],
"borrower": {
"city": "Alexandria",
"state": "VA",
"name": "Bear Houses, LLC",
"zip": "22312",
"address": "301 N Beauregard St Apt 203"
},
"txtTemplate": "statement.txt.jinja"
}

View File

@@ -0,0 +1,137 @@
{
"htmlTemplate": "statement.pdf.jinja",
"parameters": {
"monthly_payment": 175.55,
"interest_rate": 4.5,
"start_interest_date": "2020-07-15",
"payment_day_of_month": "15",
"first_payment_month": "2020-08-15",
"account_number": "100003",
"periods": 60,
"start_date": "2020-07-15",
"periods_per_year": 12,
"principal": 16884
},
"format": "html",
"email": {
"body": "Your loan statement is attached.",
"to_address": "grady@gradystreet.com",
"from_address": "jkent3rd@gmail.com",
"text": "Please see your most recent account statement.",
"server": "smtp.gmail.com",
"send_text": "true",
"template": "./template.txt",
"send_pdf": "true",
"password": "builcuouzobxroow",
"subject": "Your loan statement..."
},
"lender": {
"city": "Washington",
"name": "Rivanna Graphite Investments, LLC",
"zip": "20008",
"phone": "703.343.0782",
"state": "DC",
"address": "3100 Connecticut Ave NW Apt 144"
},
"header": {
"date": "Today",
"title": "Mortgage Loan Statement - 5025 Wayneland Drive Apt D11 Jackson MS 39211"
},
"payments": [
[
"2020-08-27",
"175.55"
],
[
"2020-09-30",
"175.55"
],
[
"2020-10-19",
"175.55",
"0.00"
],
[
"2020-12-01",
"0",
"17.55"
],
[
"2020-12-29",
"0",
"17.55"
],
[
"2021-01-20",
"175.55",
"0"
],
[
"2021-02-15",
"0.00",
"17.55"
],
[
"2021-03-12",
"175.55",
"0.00"
],
[
"2021-04-15",
"0",
"17.55"
],
[
"2021-05-17",
"0",
"17.55"
],
[
"2021-06-30",
"0",
"0.00"
],
[
"2021-07-29",
"175.55",
"17.55"
],
[
"2021-08-09",
"175.55",
"0"
],
[
"2021-08-10",
"175.55",
"0",
"extra"
],
[
"2021-09-03",
"175.55",
"0.00",
"False"
],
[
"2021-09-17",
"175.55",
"0.00",
"False"
],
[
"2021-09-18",
"0",
"0.00",
"False"
]
],
"borrower": {
"city": "Alexandria",
"state": "VA",
"name": "Bear Houses, LLC",
"zip": "22312",
"address": "301 N Beauregard St Apt 203"
},
"txtTemplate": "statement.txt.jinja"
}

View File

@@ -1,130 +0,0 @@
import json
from decimal import *
from datetime import *
# this program reads the json file describing a mortgage and calculate the actual and projected amortization
# the first payment in the file should be the interest paid at closing
# the interest at closing covers interest that would be incurred from the date of closing until the first day
# that will be covered by the first loan payment
# i.e., the first loan payment is expected a month and a half after closing, the first two weeks interest is due
# at closing. The first payment will incur interest from one month before the bill is due.
#read in the file
#filename = "./10Kloan.txt"
filename = "./dadmortgage.txt"
#filename = "./brendamortgage.txt"
#filename = "./greenfield_mortgage.txt"
if filename:
with open(filename, 'r') as f:
datastore = json.load(f)
#read in the loan profile information
annual_rate = Decimal(datastore['interest rate'])/100
daily_interest_rate = annual_rate/360
principal = Decimal(datastore["principal"]).quantize(Decimal("1.00"))
periods_per_year = Decimal(datastore["periods per year"])
total_periods = Decimal(datastore["periods"])
payment_day_of_month = int(datastore['payment day of month'])
if "monthly_payment" in datastore:
monthly_payment = Decimal(datastore["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"))
print "Principal: ", principal
print "Interest Rate: ", annual_rate
print "Payments Per Year: ", periods_per_year
print "Loan Term: ", total_periods
print "Monthly Payment", monthly_payment
print "Payments due day: ", payment_day_of_month
print
#loop over the payments and calculate the actual amortization
actual_payments = datastore["payments"]
remaining_principal = principal
annual_interest = 0
total_interest = 0
interest_paid_through_date = datetime.strptime(datastore["start_interest_date"], "%Y-%m-%d").date()
next_payment_date = datetime.strptime(datastore["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)
old_bill_date = next_bill_date
payment_number = 1
current_year = next_bill_date.year
print "Payment History:"
print "\tBill\t\t\tPayment\tDays of\tPayment\t\tRemaining\t\tPrincipal\tInterest\t\t\tNew"
print "#\tDate\t\t\tDate\tInterst\tAmount\t\tPrincipal\t\tPayment\t\tPayment\t\t\tBalance"
for payment in actual_payments:
payment_date = datetime.strptime((payment[0]), '%Y-%m-%d').date()
payment_amount = Decimal(payment[1]).quantize(Decimal("1.00"))
#print next_bill_date, "\t", next_payment_date, "\t",
days_since_last_payment = (payment_date-interest_paid_through_date).days
#print days_since_last_payment
new_interest = (days_since_last_payment * remaining_principal * daily_interest_rate).quantize(Decimal("0.00"))
# new_principal = monthly_payment - new_interest
new_principal = payment_amount - new_interest
print payment_number, " ", next_bill_date, "\t\t", payment_date, "\t", days_since_last_payment, "\t", payment_amount, "\t\t\t", remaining_principal, "\t\t", new_principal,\
"\t\t", 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
print "\t\t\t", remaining_principal
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)
print "Total interest paid to date for", old_bill_date.year, " is", annual_interest
else:
next_bill_date = date(year=old_bill_date.year+1, month = 1, day=payment_day_of_month)
print "Total interest for ", old_bill_date.year, " was ", annual_interest
annual_interest = 0
print "Total interest paid to date on this loan is", total_interest
#loop over remaining scheduled payments and present estimated amortization
print "\nEstimated future amortization"
while (payment_number <= total_periods) and (remaining_principal > 0):
print payment_number, "\t",
payment_number = payment_number+1
print next_bill_date,
days_since_last_payment = (next_bill_date-interest_paid_through_date).days
print days_since_last_payment, "\t",
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
print monthly_payment, "\t\t\t", remaining_principal, "\t\t", new_principal,\
"\t\t", new_interest,
remaining_principal = remaining_principal - new_principal
print "\t", remaining_principal
interest_paid_through_date = next_bill_date
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)
if remaining_principal > 0:
print 'Balloon payment due:', remaining_principal

View File

@@ -0,0 +1,68 @@
'''''''take in the csv file of transactions'''
import datetime
from decimal import *
import csv
from calendar import monthrange
def main():
getcontext().prec=10
open_balance = Decimal("-24521.81")
interest_rate = Decimal("8.00")/Decimal("100.00")/Decimal("365.00")
year = 2021
start_date = datetime.date(year, 1, 1)
end_date = datetime.date(year, 12, 31)+datetime.timedelta(1)
days_in_year = (end_date - start_date).days
print ("Days in year: %d" % days_in_year)
balances = [Decimal('0.00')] * days_in_year
interest_invoices = [Decimal('0.00')] * 12
with open("/Users/john/Downloads/query_result.csv") as f:
reader = csv.DictReader(f)
for row in reader:
print(row)
year = row['year']
month = row['month']
day = row['day']
amount = row['position (USD)']
date_val = datetime.date(int(year), int(month), int(day))
day_of_year = date_val.toordinal() - datetime.date(date_val.year, 1, 1).toordinal() + 1
print("Record created for day of year %d" % day_of_year)
print("%s - %s - %s: %s " % (year, month, day, amount))
balances[day_of_year] = balances[day_of_year] + Decimal(amount)
current_balance = open_balance
day_being_processed = 1
accumlated_interest = Decimal('0.000000')
while (day_being_processed <= days_in_year):
print("Day of year: %d" % day_being_processed)
#calculate the day's balance
current_balance = current_balance + balances[day_being_processed-1]
balances[day_being_processed-1] = current_balance
#accummulate the day's interest
todays_interest = current_balance * interest_rate
accumlated_interest = accumlated_interest + todays_interest
#check if this is the last day of the month, if it is do the transition stuff
current_month = datetime.date.fromordinal(start_date.toordinal()+day_being_processed-1).month
next_month = datetime.date.fromordinal(start_date.toordinal()+day_being_processed).month
print("Current Month: %d Next_month: %d" % (current_month, next_month ))
if (current_month != next_month):
interest_invoices[current_month-1] = accumlated_interest.quantize(Decimal('.01'))
current_balance = current_balance + accumlated_interest
accumlated_interest = Decimal('0.00')
day_being_processed = day_being_processed + 1
i=0
while (i<12):
print ("%d: %s" %(i+1, interest_invoices[i]) )
i = i +1
if __name__ == '__main__':
main()

View File

@@ -1,345 +0,0 @@
import json
from decimal import *
from datetime import *
from jinja2 import Environment, FileSystemLoader
from fpdf import FPDF, HTMLMixin
import smtplib
from email.MIMEMultipart import MIMEMultipart
from email.MIMEText import MIMEText
from email.MIMEBase import MIMEBase
from email import Encoders
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
env = Environment(loader=FileSystemLoader('.'))
template = env.get_template(template_fileName)
print loanModel
report = template.render(model=loanModel)
return report
def generatePDFAttachment():
template_filename = "statement.pdf.jinja"
pass
def generateEmail(from_address, to_address, subject, body, pdfAttachment, txtAttachment):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = to_address
msg.attach(MIMEText(body))
if (pdfAttachment != None):
part = MIMEBase("application", "octet-stream")
part.set_payload(pdfAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"')
msg.attach(part)
if (txtAttachment != None):
part = MIMEBase("text", "html")
part.set_payload(txtAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.html"')
msg.attach(part)
return msg
def generateEmailWithAttachments(from_address, to_address, subject, body, pdfAttachment, htmlAttachment, txtAttachment):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = to_address
msg.attach(MIMEText(body))
if (pdfAttachment != None):
part = MIMEBase("application", "octet-stream")
part.set_payload(pdfAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.pdf"')
msg.attach(part)
if (txtAttachment != None):
part = MIMEBase("text", "html")
part.set_payload(txtAttachment)
Encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="statement.html"')
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(loan):
templateKey = loan["datastore"]["format"] + "Template"
if templateKey in loan:
template = loan[templateKey]
else:
template = 'statement.pdf.jinja'
return template
def main():
test_flag = True
# this program reads the json file describing a mortgage and calculate the actual and projected amortization
# the first payment in the file should be the interest paid at closing
# the interest at closing covers interest that would be incurred from the date of closing until the first day
# that will be covered by the first loan payment
# i.e., the first loan payment is expected a month and a half after closing, the first two weeks interest is due
# at closing. The first payment will incur interest from one month before the bill is due.
# read in the file
test_flag = False
test_address = 'jkent3rd@yahoo.com'
#filename = "./testloan.json"
filename = "./9Kloan.json"
#filename = "./dadmortgage.json"
#filename = "./brendamortgage.json"
#filename = "./greenfield_mortgage.json"
template_filename = "statement.pdf.jinja"
loan = loadLoanInformation(filename)
amortizeLoan(loan)
report = transformTemplate(selectTemplate(loan), loan)
if loan["email"]["send_pdf"] == "true":
pdfAttachment = createPDF(report)
# send email
emailParameters = loan["email"]
msg = generateEmail(emailParameters["from_address"], emailParameters["to_address"], emailParameters["subject"],
emailParameters["body"], pdfAttachment, report)
if test_flag == False:
sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
else:
sendEmail(msg, emailParameters["from_address"], test_address, emailParameters['password'])
if __name__ == '__main__':
main()

View File

@@ -1,7 +1,6 @@
<html>
<head>
<link rel="stylesheet" href="//code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
<link rel="stylesheet" href="/resources/demos/style.css">
<script src="https://code.jquery.com/jquery-1.12.4.js"></script>
<script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script>
<script>
@@ -13,7 +12,7 @@
<p id="header" align="center"><font face="Arial" size=+2 >Web Mortgage Manager</font></p>
<form>
<table><tr>
<td>Loan:</td>
<td><font face="Arial">Loan:</font></td>
<td>
<select name="loan" id="loan">
{% for loan in loans %}
@@ -25,6 +24,7 @@
{% endfor %}
</select>
<button>Select</button>
<button id='manageLoans'>Manage</button>
</td>
</table>
</form>
@@ -45,6 +45,7 @@
<li><a href="#loan_amortization">Future Amortization</a></li>
<li><a href="#email">Email Statement</a></li>
<li><a href="#add_payment">Add Payment</a></li>
<li><a href="#add_extra_payment">Add Payment</a></li>
</ul>
<div id="loan_information">
<table align="center" border="1px">
@@ -176,10 +177,25 @@
<tr><td><input type="text" name="amount"></td></tr>
<tr><td bgcolor="beige">Late Fee Amount:</td></tr>
<tr><td><input type="text" name="latefee"></td></tr>
<tr><td><input name="notify" type="checkbox" value="notify"/>Send Payment Recorded Notification</td></tr>
<tr><td><button>Record Payment</button></td></tr>
</table>
</form>
</div>
<div id="add_extra_payment">
<form name="AddExtraPayment" method="post" action="/update_file">
<input type="hidden" name="loan" value="{{filename}}" />
<table align="center" border="1px" width="75%">
<tr><td align="center" bgcolor="darkgrey">Record an Extra Payment</td></tr>
<tr><td bgcolor="beige">Payment Date:</td></tr>
<tr><td><input type="date" name="date"</td></tr>
<tr><td bgcolor="beige">Payment Amount:</td></tr>
<tr><td><input type="text" name="amount"></td></tr>
<tr><td><input name="notify" type="checkbox" value="notify"/>Send Payment Recorded Notification</td></tr>
<tr><td><button>Record Extra Payment</button></td></tr>
</table>
</form>
</div>
</div>
</body>
</html>

View File

@@ -0,0 +1,23 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
Date: {{model.payment.today}}<br/>
From: {{model.payment.payee}}<br/>
To: {{model.payment.payer}}<br/>
RE: Payment Received<br/>
<br/>&nbsp;<br/>
You are receiving this email to confirm receipt of your recent loan payment.<br/>
<br/>&nbsp;<br/>
Amount Received: {{ "$%.2f"|format(model.payment.amount) }}<br/>
Date Received: {{model.payment.date}}<br/>
{% if model.payment.late_fee != 0 %}
Late Fee Assessed: {{model.payment.late_fee}}<br/>
{% endif %}
<br/>&nbsp;<br/>
Thank you!<br/>
</body>
</html>

View File

@@ -1,51 +1,36 @@
Original Principal Balance: {{ original_principal_balance }}
{{ model.header.title }}
{{ model.lender.name }}
{{ model.lender.address }}
{{ model.lender.city }}, {{model.lender.state }} {{ model.lender.zip }}
{{ model.lender.phone }}
Statement Date: {{ model.header.date }}
Loan Information
Borrower: {{ model.borrower.name }}
Account Number: {{ model.parameters.account_number }}
Origination Date: {{ model.parameters.start_date }}
Address:
{{ model.borrower.address }}
{{ model.borrower.city }}, {{model.borrower.state }} {{ model.borrower.zip }}
Original Principal: {{ "$%.2f"|format(model.parameters.principal) }}
Rate: {{model.parameters.interest_rate }}%
Term: {{model.parameters.periods }} months
Next Payment Due Date: {{model.parameters.next_due_date}}
Payment Due: {{ "$%.2f"|format(model.parameters.next_payment_amt) }}
Payment History
#\tBill Date
Payment Date
Days of Interest
Payment Amount
Principal Payment
Interest Payment
Late Fee
New Balance
{% for item in past_payments %}
{{ item.payment_number }}
{{ item.bill_date }}
{{ item.payment_date }}
{{ item.days_of_interest }}
{{ item.payment_amount }}
{{ item.principal_payment }}
{{ item.interest_payment }}
{{ item.late_fee }}
{{ item.new_balance }}
{% if item.print_interest_total %}
{{ item.interest_total_message }}
{% endif %}
#,Due Date,Date Paid,Days Interest,Payment Amt,Principal Pmt,Interest Pmt,Late Fee,New Balance
{% for item in model.past_payments %}
{{ item.payment_number }},{{ item.bill_date }},{{ item.payment_date }},{{ item.days_of_interest }},{{ "$%.2f"|format(item.payment_amount) }},{{ "$%.2f"|format(item.principal_payment) }},{{ "$%.2f"|format(item.interest_payment) }},{{ "$%.2f"|format(item.late_fee) }},{{ "$%.2f"|format(item.new_balance) }}
{% if item.month == 12 or loop.last %}
Total interest paid in {{item.year}} is {{ "$%.2f"|format(item.annual_interest_to_date) }}.
{% endif %}
{% endfor %}
Total interest paid to date is {{ total_interest_paid_to_date }}.
Total interest paid to date is {{ "$%.2f"|format(model.total_interest_paid_to_date) }}.
Remaining Amortization
#
Days of Interest
Due Date
Payment Amount
Scheduled Principal Payment
Scheduled Interest Payment
Expected New Balance
{% for item in future_payments %}
{{ item.payment_number }}
{{ item.days_of_interest }}
{{ item.payment_date }}
{{ item.payment_amount }}
{{ item.principal_payment }}
{{ item.interest_payment }}
{{ item.new_balance }}
#,Due Date,Days Interest,Payment Amt,Principal Pmt,Interest Pmt,Principal Balance<
{% for item in model.future_payments %}
{{ item.payment_number }},{{ item.payment_date }},{{ item.days_of_interest }},{{ "$%.2f"|format(item.payment_amount) }},{{ "$%.2f"|format(item.principal_payment) }},{{ "$%.2f"|format(item.interest_payment) }},{{ "$%.2f"|format(item.new_balance) }}
{% endfor %}
Balloon Payment Due: {{ balloon_payment }}
Balloon Payment Due: {{ "$%.2f"|format(model.balloon_payment) }}

View File

@@ -1,31 +1,32 @@
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 flask import Flask, render_template, request, redirect
from decimal import *
from datetime import *
from fpdf import FPDF, HTML2FPDF, HTMLMixin
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
module_directory = os.path.dirname(__file__)
app_directory = os.path.normpath(os.path.join(module_directory, ".."))
loader = jinja2.FileSystemLoader([os.path.join(module_directory, "templates")])
environment = jinja2.Environment(loader=loader)
app = Flask(__name__)
######
# Flask Call Backs
######
@app.route('/')
def hello():
loans = getLoanFiles(app_directory)
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 a loan was not specified, choose the first loan and reload page with it
if 'loan' in request.args:
filename = request.args["loan"]
else:
@@ -36,42 +37,56 @@ def hello():
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))
late_fee = Decimal('0.00').quantize(Decimal('1.00'))
extra_payment = False
payment_amount = Decimal('0.00').quantize(Decimal('1.00'))
payment_history = data["payments"]
todays_date = str(datetime.now().strftime("%Y-%m-%d"))
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 + ".")
if request.form['date'] == '':
payment_date = todays_date
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_date = todays_date
messages.append("No date was provided. Assuming today's date of " + payment_date + ".")
payment_amount = Decimal('0.00')
proceed_flag = True
if 'amount' in request.form:
if (request.form['amount'] != ''):
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 'latefee' in request.form:
if request.form['latefee'] != '':
try:
late_fee = Decimal(request.form['latefee'])
messages.append("Late fee amount: " + str(late_fee) + ".")
except:
messages.append("The late fee amount was specified but could not be interpreted. " +
"Your payment was not recorded.")
proceed_flag = False
if 'extra' in request.form:
if request.form['extra'] != '':
extra_payment = True
if proceed_flag is True:
try:
backup_filename = loanFile + ".backup-" + datetime.now().strftime("%Y-%m-%d %H-%M-%S") + ".json"
@@ -82,22 +97,47 @@ def update_file():
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 +"'" )
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)])
payment_history.append([payment_date, str(payment_amount), str(late_fee), str(extra_payment)])
file = open(getFullPathofLoanFile(loanFile), 'w+')
json.dump(data, file, indent=2)
file.close()
except:
messages.append("An error occurred writing to the file. Your payment file may be corrupt, " + \
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. ")
# see if an email notification was requested, if not, exit now
if 'notify' not in request.form:
messages.append("Payment notification email not requested.")
return render_template('add.html', filename=loanFile, messages=messages)
messages.append("Payment notification email requested.")
# send email
emailParameters = data["email"]
result = {}
payment = {}
result['payment'] = payment
payment['today'] = todays_date
payment['date'] = payment_date
payment['payer'] = data['borrower']['name']
payment['payee'] = data['lender']['name']
payment['amount'] = payment_amount
payment['late_fee'] = late_fee
payment['extra_payment'] = extra_payment
subject = "Your loan payment has been received"
html = transformTemplate(selectTemplate('paymentNotification'), result)
msg = generatePaymentNotificationEmail(emailParameters["from_address"], emailParameters["to_address"],
subject, html)
sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
return render_template('add.html', filename=loanFile, messages=messages)
@app.route('/send_statement', methods=['POST'])
def send_statement():
loanFile = request.form["loan"]
@@ -122,32 +162,67 @@ def send_statement():
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)
msg = generateStatementEmail(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):
######
# Loan File Functions
######
def getLoanFiles(directory):
loans = []
count = 0
directory = os.path.normpath(directory)
print("Directory {}".format(directory))
fileNames = os.listdir(directory)
fileNames.sort()
for filename in fileNames:
if filename.endswith(".loan"):
displayName = filename.removesuffix(".loan").replace("_"," ")
loans.append(createLoanEntryMenuItem("{0}".format(displayName), filename))
#loans.append(createLoanEntryMenuItem("1 Brenda Mortgage", "brendamortgage.json"))
#loans.append(createLoanEntryMenuItem("2 Dad Mortgage", "Dad-Mortgage.loan"))
#loans.append(createLoanEntryMenuItem("3 839 Harbor Bend Loan 1", "HarborBend839-2022.json"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Bear Houses LLC Loan", "9Kloan.loan"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Test Loan", "testloan.json"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Greenfield Lane Mortgage", "greenfield_mortgage.json"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Greenfield Lane Refinance", "greenfield_refinance.json"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Harbor Bend Loan 1", "BearHousesLLC-22-0001.json"))
#loans.append(createLoanEntryMenuItem("--ARCHIVE-- Wayneland Dr D11 Mortgage", "wayneland_mortgage.json"))
return loans
def createLoanEntryMenuItem(loanName, fileName):
x = {}
x['name'] = loanName
x['filename'] = fileName
return x
def getFullPathofLoanFile(filename):
return '/Users/john/PycharmProjects/mortgage/' + filename
###################
# from old code #
###################
def getFullPathofLoanFile(filename):
app_path = os.path.dirname(__file__)
file_path = os.path.normpath(os.path.join(os.path.dirname(__file__),'..'))
print("App Path: {0}\nFile Path: {1}".format(app_path, file_path))
return os.path.join(file_path, filename)
######
# Datastore Manipulation Functions
######
def getStatementHeader(datastore):
return datastore['header']
@@ -179,7 +254,7 @@ def getLoanParameters(datastore):
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'])
#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"))
@@ -190,8 +265,8 @@ def getLoanParameters(datastore):
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['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
@@ -224,6 +299,10 @@ def getDatastore(filename=None):
return datastore
######
# Loan Calculation Functions
######
def amortizeLoan(loan):
# loop over the payments and calculate the actual amortization
@@ -242,8 +321,8 @@ def amortizeLoan(loan):
payment_number = 1
annual_interest = 0
total_interest = 0
old_bill_date = next_bill_date
current_year = next_bill_date.year
#old_bill_date = next_bill_date
#current_year = next_bill_date.year
past_payments = []
future_payments = []
@@ -252,15 +331,16 @@ def amortizeLoan(loan):
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
if len(payment)>2:
if len(payment) > 2:
late_fee = Decimal(payment[2]).quantize(Decimal("1.00"))
else:
late_fee = Decimal("0.00")
#check for out of order payments, generally a sign of a data entry problem, especially years
# 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))
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"))
@@ -270,11 +350,10 @@ def amortizeLoan(loan):
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['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
@@ -289,16 +368,24 @@ def amortizeLoan(loan):
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)
print("Payment record length: " + str(len(payment)))
#check for the extra payment flag, if its there, don't advance the next payment date
if len(payment) > 3 and payment[3] == "extra":
print("Extra payment flag: " + payment[3])
#this is an extra payment don't advance the date
else:
annual_interest = Decimal("0.00")
next_bill_date = date(year=old_bill_date.year + 1, month=1, day = payment_day_of_month)
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["parameters"]["next_due_date"] = next_bill_date
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
@@ -338,12 +425,17 @@ def amortizeLoan(loan):
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
######
# Report Generation Functions
######
def transformTemplate(template_fileName, loanModel):
# template_filename = "statement.text.jinja"
# setup jinja for creating the statement
@@ -354,7 +446,7 @@ def transformTemplate(template_fileName, loanModel):
return report
def generateEmail(from_address, to_address, subject, body, pdf, html, txt):
def generateStatementEmail(from_address, to_address, subject, body, pdf, html, txt):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
@@ -362,21 +454,21 @@ def generateEmail(from_address, to_address, subject, body, pdf, html, txt):
msg.attach(MIMEText(body))
if (pdf != None):
if pdf is not 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):
if html is not 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):
if txt is not None:
part = MIMEBase("text", "plain")
part.set_payload(txt)
encoders.encode_base64(part)
@@ -386,6 +478,22 @@ def generateEmail(from_address, to_address, subject, body, pdf, html, txt):
return msg
def generatePaymentNotificationEmail(from_address, to_address, subject, html):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = to_address
#msg.attach(MIMEText(body))
part = MIMEBase("text", "html")
part.set_payload(html)
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="notification.html"')
msg.attach(part)
return msg
def sendEmail(msg, from_address, to_address, passwd):
try:
server = smtplib.SMTP('smtp.gmail.com', 587)
@@ -401,18 +509,20 @@ def sendEmail(msg, from_address, to_address, passwd):
def createPDF(report):
# create pdf
class MyFPDF(FPDF, HTMLMixin):
class PDF(FPDF, HTMLMixin):
pass
pdf = MyFPDF()
pdf = PDF()
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 == 'paymentNotification':
return 'payment_received_email.html.jinja'
if format == 'html':
return 'statement.html.jinja'
@@ -424,9 +534,11 @@ def selectTemplate(format):
return 'statement.html.jinja'
def main():
app.debug = True
app.run()
if __name__ == '__main__':
main()
main()

544
mortgage/web_ng.py Normal file
View File

@@ -0,0 +1,544 @@
import json
import jinja2
import smtplib
import os
from flask import Flask, render_template, request, redirect
from decimal import *
from datetime import *
from fpdf import FPDF, HTMLMixin
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.mime.base import MIMEBase
from email import encoders
module_directory = os.path.dirname(__file__)
app_directory = os.path.normpath(os.path.join(module_directory, ".."))
loader = jinja2.FileSystemLoader([os.path.join(module_directory, "templates")])
environment = jinja2.Environment(loader=loader)
app = Flask(__name__)
######
# Flask Call Backs
######
@app.route('/')
def hello():
loans = getLoanFiles(app_directory)
#if a loan was not specified, choose the first loan and reload page with it
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))
late_fee = Decimal('0.00').quantize(Decimal('1.00'))
extra_payment = False
payment_amount = Decimal('0.00').quantize(Decimal('1.00'))
payment_history = data["payments"]
todays_date = str(datetime.now().strftime("%Y-%m-%d"))
if 'date' in request.form:
if request.form['date'] == '':
payment_date = todays_date
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:
payment_date = todays_date
messages.append("No date was provided. Assuming today's date of " + payment_date + ".")
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:
messages.append("The amount provided could not be interpreted. Your payment was not recorded.")
proceed_flag = False
else:
pass
if 'latefee' in request.form:
if request.form['latefee'] != '':
try:
late_fee = Decimal(request.form['latefee'])
messages.append("Late fee amount: " + str(late_fee) + ".")
except:
messages.append("The late fee amount was specified but could not be interpreted. " +
"Your payment was not recorded.")
proceed_flag = False
if 'extra' in request.form:
if request.form['extra'] != '':
extra_payment = True
if proceed_flag is True:
try:
backup_filename = loanFile + ".backup-" + datetime.now().strftime("%Y-%m-%d %H-%M-%S") + ".json"
backup_file = open(getFullPathofLoanFile(backup_filename), 'w+')
json.dump(data, backup_file, indent=2)
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), str(late_fee), str(extra_payment)])
file = open(getFullPathofLoanFile(loanFile), 'w+')
json.dump(data, file, indent=2)
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. ")
# see if an email notification was requested, if not, exit now
if 'notify' not in request.form:
messages.append("Payment notification email not requested.")
return render_template('add.html', filename=loanFile, messages=messages)
messages.append("Payment notification email requested.")
# send email
emailParameters = data["email"]
result = {}
payment = {}
result['payment'] = payment
payment['today'] = todays_date
payment['date'] = payment_date
payment['payer'] = data['borrower']['name']
payment['payee'] = data['lender']['name']
payment['amount'] = payment_amount
payment['late_fee'] = late_fee
payment['extra_payment'] = extra_payment
subject = "Your loan payment has been received"
html = transformTemplate(selectTemplate('paymentNotification'), result)
msg = generatePaymentNotificationEmail(emailParameters["from_address"], emailParameters["to_address"],
subject, html)
sendEmail(msg, emailParameters["from_address"], emailParameters["to_address"], emailParameters['password'])
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 = generateStatementEmail(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)
######
# Loan File Functions
######
def getLoanFiles(directory):
loans = []
count = 0
directory = os.path.normpath(directory)
print("Directory {}".format(directory))
for filename in os.listdir(directory):
if filename.endswith(".loan"):
count=count+1
loans.append(createLoanEntryMenuItem("{0} {1}".format(count, filename.removesuffix(".loan")), filename))
return loans
def createLoanEntryMenuItem(loanName, fileName):
x = {}
x['name'] = loanName
x['filename'] = fileName
return x
def getFullPathofLoanFile(filename):
app_path = os.path.dirname(__file__)
file_path = os.path.normpath(os.path.join(os.path.dirname(__file__),'..'))
print("App Path: {0}\nFile Path: {1}".format(app_path, file_path))
return os.path.join(file_path, filename)
######
# Datastore Manipulation Functions
######
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
######
# Loan Calculation Functions
######
def new_amortizeLoan(loan):
#loop over the historical data and re-calculate the actual amortization
monthly_payment = loan["parameters"]["monthly_payment"]
first_day_interest_accrues = 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()
def amortizeLoan(loan):
# loop over the payments and calculate the actual amortization
monthly_payment = loan["parameters"]["monthly_payment"]
actual_payments = loan["datastore"]["payments"]
#invoices = loan["datastore"]["invoices"]
#payments = loan["datastore"]["payments"]
#allocations = loan["datastore"]["allocations"]
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
if len(payment) > 2:
late_fee = Decimal(payment[2]).quantize(Decimal("1.00"))
else:
late_fee = Decimal("0.00")
# 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 - late_fee
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 - late_fee
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
payment_record['late_fee'] = late_fee
past_payments.append(payment_record)
payment_number = payment_number + 1
print("Payment record length: " + str(len(payment)))
#check for the extra payment flag, if its there, don't advance the next payment date
if len(payment) > 3 and payment[3] == "extra":
print("Extra payment flag: " + payment[3])
#this is an extra payment don't advance the date
else:
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["parameters"]["next_due_date"] = next_bill_date
loan["total_interest_paid_to_date"] = total_interest
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
######
# Report Generation Functions
######
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 generateStatementEmail(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 is not 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 is not 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 is not 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 generatePaymentNotificationEmail(from_address, to_address, subject, html):
msg = MIMEMultipart()
msg['Subject'] = subject
msg['From'] = from_address
msg['To'] = to_address
#msg.attach(MIMEText(body))
part = MIMEBase("text", "html")
part.set_payload(html)
encoders.encode_base64(part)
part.add_header('Content-Disposition', 'attachment; filename="notification.html"')
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)
return pdf.output(dest='S')
def selectTemplate(format):
if format == 'paymentNotification':
return 'payment_received_email.html.jinja'
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'
def main():
app.debug = True
app.run()
if __name__ == '__main__':
main()

3
pyvenv.cfg Normal file
View File

@@ -0,0 +1,3 @@
home = /opt/local/bin
include-system-site-packages = false
version = 3.10.7

View File

@@ -1,3 +1,3 @@
flask
fpdf
jinja2
fpdf2
jinja2