:G: Enter commit message. Lines beginning with 'HG:' are removed.
This commit is contained in:
@@ -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"
|
||||
}
|
||||
}
|
||||
69
10Kloan.json
69
10Kloan.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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"
|
||||
}
|
||||
@@ -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": {
|
||||
@@ -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": {
|
||||
128
_Archive_Greenfield_Lane_Refinance-1.loan
Normal file
128
_Archive_Greenfield_Lane_Refinance-1.loan
Normal 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"
|
||||
}
|
||||
50
_Archive_Old_Harbor_Bend_Mortgage.loan
Normal file
50
_Archive_Old_Harbor_Bend_Mortgage.loan
Normal 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"
|
||||
}
|
||||
137
_Archive_Wayneland_Mortgage.loan
Normal file
137
_Archive_Wayneland_Mortgage.loan
Normal 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"
|
||||
}
|
||||
130
mortgage.py
130
mortgage.py
@@ -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
|
||||
68
mortgage/calculate_interest_heloc.py
Normal file
68
mortgage/calculate_interest_heloc.py
Normal 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()
|
||||
@@ -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()
|
||||
@@ -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>
|
||||
23
mortgage/templates/payment_received_email.html.jinja
Normal file
23
mortgage/templates/payment_received_email.html.jinja
Normal 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/> <br/>
|
||||
You are receiving this email to confirm receipt of your recent loan payment.<br/>
|
||||
<br/> <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/> <br/>
|
||||
Thank you!<br/>
|
||||
</body>
|
||||
</html>
|
||||
@@ -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) }}
|
||||
|
||||
254
mortgage/web.py
254
mortgage/web.py
@@ -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
544
mortgage/web_ng.py
Normal 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
3
pyvenv.cfg
Normal file
@@ -0,0 +1,3 @@
|
||||
home = /opt/local/bin
|
||||
include-system-site-packages = false
|
||||
version = 3.10.7
|
||||
@@ -1,3 +1,3 @@
|
||||
flask
|
||||
fpdf
|
||||
jinja2
|
||||
fpdf2
|
||||
jinja2
|
||||
|
||||
Reference in New Issue
Block a user