|
|
@@ -0,0 +1,386 @@ |
|
|
|
<template> |
|
|
|
<div id="new" class="page"> |
|
|
|
|
|
|
|
<h2>New Loan</h2> |
|
|
|
|
|
|
|
<section class="loans-list"> |
|
|
|
|
|
|
|
<h3 v-for="(l, indx) in loans" |
|
|
|
:class="sel == indx ? 'sel' : ''" |
|
|
|
@click="() => sel = indx" |
|
|
|
> |
|
|
|
{{l.title}} |
|
|
|
</h3> |
|
|
|
|
|
|
|
<div class="add"> |
|
|
|
<svg @click="create" |
|
|
|
xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" |
|
|
|
class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 |
|
|
|
0 1 0 1h-3v3a.5.5 0 0 1-1 0v-3h-3a.5.5 0 0 1 0-1h3v-3A.5.5 0 0 1 8 4z"/> </svg> |
|
|
|
</div> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
<h3>Loan</h3> |
|
|
|
<label>Name</label> |
|
|
|
<input :value="loans[sel].title" required |
|
|
|
@input="(e) => loans[sel].title = stripLetters(e)"> |
|
|
|
<button @click="del">Delete</button> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
|
|
|
|
<div class="hint"> |
|
|
|
<img class="icon" src="/assets/image/icon/question-circle.svg" alt=""> |
|
|
|
<div class="tooltip"> |
|
|
|
<p>Assumes borrower is not self employed, not bankrupt in the past 7 |
|
|
|
years, a citizen, and intends to occupy the property.</p> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<h3>Borrower</h3> |
|
|
|
<label>Number of Borrowers</label> |
|
|
|
<input :value="estimate.borrowers" |
|
|
|
@input="(e) => estimate.borrowers = stripInt(e)"> |
|
|
|
<label>Credit Score</label> |
|
|
|
<input :value="estimate.creditScore" |
|
|
|
@input="(e) => estimate.creditScore = stripInt(e)"> |
|
|
|
<label>Monthly Income ($)</label> |
|
|
|
<input :value="estimate.mIncome" |
|
|
|
@input="(e) => estimate.mIncome = strip(e)"> |
|
|
|
|
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="radios form"> |
|
|
|
<h3>Transaction Type</h3> |
|
|
|
<input selected type="radio" name="transaction_type" value="0" |
|
|
|
v-model="estimate.transaction" > |
|
|
|
<label>Purchase</label> |
|
|
|
<input type="radio" name="transaction_type" value="1" |
|
|
|
v-model="estimate.transaction"> |
|
|
|
<label>Refinance</label> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
<h3>Property Details</h3> |
|
|
|
<label>Price ($)</label> |
|
|
|
<input :value="estimate.price" @input="setPrice"> |
|
|
|
<label>Type</label> |
|
|
|
<select id="" name="" v-model="estimate.property"> |
|
|
|
<option value="attched">Single Family Attached</option> |
|
|
|
<option value="detached">Single Family Detached</option> |
|
|
|
<option value="lorise">Lo-rise (4 stories or less)</option> |
|
|
|
<option value="hirise">Hi-rise (over 4 stories)</option> |
|
|
|
</select> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="radios form"> |
|
|
|
<h3>Loan Type</h3> |
|
|
|
<input type="radio" name="loan_type" value="conv" v-model="loans[sel].type" |
|
|
|
> |
|
|
|
<label>Conventional</label> |
|
|
|
<input type="radio" name="loan_type" value="fha" v-model="loans[sel].type"> |
|
|
|
<label>FHA</label> |
|
|
|
<input type="radio" name="loan_type" value="va" v-model="loans[sel].type"> |
|
|
|
<label>VA</label> |
|
|
|
<input type="radio" name="loan_type" value="usda" v-model="loans[sel].type"> |
|
|
|
<label>USDA</label> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
<h3>Loan Details</h3> |
|
|
|
<label>Loan Term (years)</label> |
|
|
|
<input :value="loans[sel].term" |
|
|
|
@input="(e) => loans[sel].term = strip(e)"> |
|
|
|
|
|
|
|
<label>Loan Program</label> |
|
|
|
<select id="" name="" v-model="loans[sel].program"> |
|
|
|
<option value="none">None</option> |
|
|
|
</select> |
|
|
|
|
|
|
|
<label>Loan to Value (%)</label> |
|
|
|
<input :value="loans[sel].ltv" @input="setLtv"> |
|
|
|
<label>Loan Amount ($)</label> |
|
|
|
<input :value="loans[sel].amount" |
|
|
|
@input="setAmount"> |
|
|
|
<label>Housing Expense DTI (%) - Optional</label> |
|
|
|
<input :value="loans[sel].housingDti" @input="setHousingDti"> |
|
|
|
<label>Total DTI (%) - Optional</label> |
|
|
|
<input :value="loans[sel].dti" @input="setDti"> |
|
|
|
<label>Home Owner's Association ($/month)</label> |
|
|
|
<input :value="loans[sel].hoa" |
|
|
|
@input="(e) => { loans[sel].hoa = strip(e) }"> |
|
|
|
|
|
|
|
<label>Interest Rate (%)</label> |
|
|
|
<input :value="loans[sel].interest" |
|
|
|
@input="(e) => { loans[sel].interest = stripPerc(e) }"> |
|
|
|
<label>Days of Interest</label> |
|
|
|
<input :value="loans[sel].interestDays" |
|
|
|
@input="(e) => {loans[sel].interestDays = stripInt(e)}"> |
|
|
|
|
|
|
|
<label>Hazard Insurance Escrow (months)</label> |
|
|
|
<input :value="loans[sel].hazardEscrow" |
|
|
|
@input="(e) => {loans[sel].hazardEscrow = stripInt(e)}"> |
|
|
|
<label>Hazard Insurance ($/month)</label> |
|
|
|
<input :value="loans[sel].hazard" |
|
|
|
@input="(e) => {loans[sel].hazard = strip(e)}"> |
|
|
|
|
|
|
|
<label>Real Estate Tax Escrow (months)</label> |
|
|
|
<input :value="loans[sel].taxEscrow" |
|
|
|
@input="e => {loans[sel].taxEscrow = stripInt(e)}"> |
|
|
|
<label>Real Estate Tax ($/month)</label> |
|
|
|
<input :value="loans[sel].tax" |
|
|
|
@input="(e) => {loans[sel].tax = strip(e)}"> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
<h3>Fees</h3> |
|
|
|
<div v-for="(fee, indx) in estimate.loans[sel].fees" |
|
|
|
:key="fee.name + indx" class="fee" |
|
|
|
> |
|
|
|
<label> |
|
|
|
${{fee.amount}}{{ fee.perc ? ` ${fee.perc}%` : ''}} - {{fee.name}}<br> |
|
|
|
{{fee.type}} |
|
|
|
</label> |
|
|
|
<img width="21" height="21" src="/assets/image/icon/x-red.svg" |
|
|
|
@click="() => estimate.loans[sel].fees.splice(indx, 1)"> |
|
|
|
</div> |
|
|
|
<button @click="resetFees">Reset</button> |
|
|
|
<button @click="createFee">New</button> |
|
|
|
</section> |
|
|
|
|
|
|
|
<fee-dialog v-if="newFee" |
|
|
|
:heading="'New Fee'" |
|
|
|
:initial="{}" |
|
|
|
:price="estimate.price" |
|
|
|
@close="() => newFee = null" |
|
|
|
@save="addFee" |
|
|
|
/> |
|
|
|
|
|
|
|
<section class="form radios"> |
|
|
|
<h3>Mortgage Insurance</h3> |
|
|
|
<input type="radio" name="transaction_type" value="transaction" |
|
|
|
@input="e => estimate.transaction = 0" |
|
|
|
selected="estimate.transaction == 0"> |
|
|
|
<label>1.43% - National MI</label> |
|
|
|
<input type="radio" name="transaction_type" value="refinance" |
|
|
|
@input="e => estimate.transaction = 1" |
|
|
|
selected="estimate.transaction == 1"> |
|
|
|
<label>0.73% - MGIC</label> |
|
|
|
</section> |
|
|
|
|
|
|
|
<section class="form inputs"> |
|
|
|
|
|
|
|
<button @click="generate">Generate</button> |
|
|
|
|
|
|
|
<div class="errors"> |
|
|
|
<span v-for="e in errors">{{e}}</span> |
|
|
|
</div> |
|
|
|
|
|
|
|
</section> |
|
|
|
|
|
|
|
</div> |
|
|
|
</template> |
|
|
|
|
|
|
|
<script> |
|
|
|
import Dialog from "./dialog.vue" |
|
|
|
import FeeDialog from "./fee-dialog.vue" |
|
|
|
import { stripLetters, strip, stripInt, stripPerc } from "../helpers.js" |
|
|
|
|
|
|
|
// The default values of a new estimate |
|
|
|
const example = { |
|
|
|
title: "Example", |
|
|
|
type: "", |
|
|
|
term: 0, |
|
|
|
ltv: 0, // Loan to home value ratio |
|
|
|
dti: 0, |
|
|
|
housingDti: 0, |
|
|
|
amount: 0, |
|
|
|
interest: 0, |
|
|
|
interestDays: 0, |
|
|
|
hazard: 0, // Hazard insurance monthly payment |
|
|
|
hazardEscrow: 0, // Hazard insurance escrow in months (0 is none) |
|
|
|
tax: 0, // Real Estate taxes monthly payment |
|
|
|
taxEscrow: 0, // Months to escrow (0 is none) |
|
|
|
hoa: 100, // Home owner's association monthly fee |
|
|
|
program: "", |
|
|
|
pud: true, // Property under development |
|
|
|
zip: '', |
|
|
|
fees: [], |
|
|
|
} |
|
|
|
|
|
|
|
// The default loans on a new estimate |
|
|
|
const loans = [ |
|
|
|
Object.assign({}, example,), |
|
|
|
Object.assign( |
|
|
|
Object.assign({}, example), |
|
|
|
{title: "Another One",} |
|
|
|
), |
|
|
|
] |
|
|
|
|
|
|
|
// Default estimate fields |
|
|
|
const estimate = { |
|
|
|
property: "", |
|
|
|
transaction: 0, |
|
|
|
price: 0, |
|
|
|
borrowers: 0, |
|
|
|
creditScore: 0, |
|
|
|
mIncome: 0, |
|
|
|
loans: loans, |
|
|
|
} |
|
|
|
|
|
|
|
const newFee = { |
|
|
|
name: '', type: '', amount: 0, perc: 0 |
|
|
|
} |
|
|
|
|
|
|
|
// Clone loan from initial example as a new loan |
|
|
|
function create() { |
|
|
|
this.estimate.loans.push( |
|
|
|
Object.assign({}, example, {fees: this.createFees()}) |
|
|
|
) |
|
|
|
} |
|
|
|
|
|
|
|
function createFees() { |
|
|
|
return this.fees.map(f => Object.assign({}, f)) |
|
|
|
} |
|
|
|
|
|
|
|
// Setup this.newFee for the creation dialog |
|
|
|
function createFee() { |
|
|
|
this.newFee = Object.assign({}, newFee) |
|
|
|
} |
|
|
|
|
|
|
|
function resetFees() { |
|
|
|
this.estimate.loans[this.sel].fees = this.createFees() |
|
|
|
} |
|
|
|
|
|
|
|
// If valid, add the current this.newFee to the array of fees and reset |
|
|
|
// this.newFee to null |
|
|
|
function addFee(fee, isDebit) { |
|
|
|
|
|
|
|
if (!isDebit) fee.amount = fee.amount * -1 |
|
|
|
this.estimate.loans[this.sel].fees.push(fee) |
|
|
|
this.newFee = null |
|
|
|
} |
|
|
|
|
|
|
|
function del() { |
|
|
|
if (this.loans.length > 1) { |
|
|
|
let x = this.sel |
|
|
|
this.sel = 0 |
|
|
|
this.loans.splice(x, 1) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Changes loan.ltv's <input> and data() values, then syncs with data.amount |
|
|
|
function setLtv(e) { |
|
|
|
let ltv = strip(e) |
|
|
|
let loan = this.loans[this.sel] |
|
|
|
if (!this.estimate.price) return |
|
|
|
|
|
|
|
if (ltv > 100) ltv = 100 |
|
|
|
if (ltv < 0) ltv = 0 |
|
|
|
|
|
|
|
loan.ltv = ltv |
|
|
|
loan.amount = (ltv / 100 * this.estimate.price).toFixed(2) |
|
|
|
} |
|
|
|
|
|
|
|
// Changes loan.amount's <input> and data() values, then syncs with data.ltv |
|
|
|
function setAmount(e) { |
|
|
|
let amount = strip(e) |
|
|
|
let loan = this.loans[this.sel] |
|
|
|
if (!this.estimate.price) return |
|
|
|
|
|
|
|
if (amount > loan.price) amount = loan.price |
|
|
|
if (amount < 0) amount = 0 |
|
|
|
|
|
|
|
loan.amount = amount |
|
|
|
loan.ltv = (amount / this.estimate.price * 100).toFixed(2) |
|
|
|
} |
|
|
|
|
|
|
|
// Updates the property price for all loans and their fee amounts. |
|
|
|
function setPrice(e) { |
|
|
|
let value = strip(e) |
|
|
|
this.estimate.price = value |
|
|
|
this.estimate.loans[this.sel].fees.forEach(fee => { |
|
|
|
if (fee.perc) fee.amount = (fee.perc / 100 * value).toFixed(2) |
|
|
|
}) |
|
|
|
} |
|
|
|
|
|
|
|
function setDti(e) { |
|
|
|
let dti = strip(e) |
|
|
|
let loan = this.loans[this.sel] |
|
|
|
if (!loan.price) return |
|
|
|
|
|
|
|
if (dti > 100) dti = 100 |
|
|
|
if (dti < 0) dti = 0 |
|
|
|
|
|
|
|
e.target.value = dti |
|
|
|
loan.dti = dti |
|
|
|
} |
|
|
|
|
|
|
|
function setHousingDti(e) { |
|
|
|
let housingDti = strip(e) |
|
|
|
let loan = this.loans[this.sel] |
|
|
|
if (!loan.price) return |
|
|
|
|
|
|
|
if (housingDti > 100) housingDti = 100 |
|
|
|
if (housingDti < 0) housingDti = 0 |
|
|
|
|
|
|
|
e.target.value = housingDti |
|
|
|
loan.housingDti = housingDti |
|
|
|
} |
|
|
|
|
|
|
|
function generate() { |
|
|
|
this.errors = this.validate() |
|
|
|
} |
|
|
|
|
|
|
|
function validate() { |
|
|
|
let errors = [] |
|
|
|
const estimate = this.estimate |
|
|
|
|
|
|
|
// Alternative attribute names for error messages |
|
|
|
const names = { |
|
|
|
term: "loan term", |
|
|
|
ltv: "loan to value", |
|
|
|
hazard: "hazard insurance", |
|
|
|
hazardEscrow: "hazard insurance escrow", |
|
|
|
} |
|
|
|
|
|
|
|
if (!estimate.property) { |
|
|
|
errors.push("Missing property type.") |
|
|
|
} else if (!estimate.price) { |
|
|
|
errors.push("Missing property price.") |
|
|
|
} else if (!estimate.borrowers) { |
|
|
|
errors.push("Missing number of borrowers.") |
|
|
|
} else if (!estimate.creditScore) { |
|
|
|
errors.push("Missing credit score.") |
|
|
|
} else if (!estimate.mIncome) { |
|
|
|
errors.push("Missing monthly income.") |
|
|
|
} |
|
|
|
|
|
|
|
return errors |
|
|
|
} |
|
|
|
|
|
|
|
// Percentage values of fees always takek precedent over amounts. The conversion |
|
|
|
// happens in setPrice() |
|
|
|
export default { |
|
|
|
components: { Dialog, FeeDialog }, |
|
|
|
methods: { |
|
|
|
setPrice, setLtv, setAmount, setDti, setHousingDti, strip, stripInt, |
|
|
|
stripLetters, del, create, createFees, createFee, resetFees, |
|
|
|
addFee, generate, validate |
|
|
|
}, |
|
|
|
props: ['user', 'fees'], |
|
|
|
data() { |
|
|
|
return { |
|
|
|
estimate: estimate, |
|
|
|
loans: estimate.loans, |
|
|
|
sel: 0, |
|
|
|
newFee: null, |
|
|
|
errors: [], |
|
|
|
} |
|
|
|
}, |
|
|
|
created() { |
|
|
|
this.estimate.loans.forEach(l => l.fees = this.createFees()) |
|
|
|
} |
|
|
|
} |
|
|
|
</script> |