@@ -66,26 +66,26 @@ | |||||
name="loan_type" | name="loan_type" | ||||
:checked="loan.type.id == 1" | :checked="loan.type.id == 1" | ||||
value="1" | value="1" | ||||
@change="(e) => $emit('update:loanType', e.target.value)" | |||||
@change="(e) => $emit('update:loanType', 1)" | |||||
> | > | ||||
<label>Conventional</label> | <label>Conventional</label> | ||||
<input type="radio" | <input type="radio" | ||||
name="loan_type" | name="loan_type" | ||||
value="2" | value="2" | ||||
:checked="loan.type.id == 2" | :checked="loan.type.id == 2" | ||||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||||
@change="(e) => $emit('update:loanType', 2)"> | |||||
<label>FHA</label> | <label>FHA</label> | ||||
<input type="radio" | <input type="radio" | ||||
name="loan_type" | name="loan_type" | ||||
value="3" | value="3" | ||||
:checked="loan.type.id == 3" | :checked="loan.type.id == 3" | ||||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||||
@change="(e) => $emit('update:loanType', 3)"> | |||||
<label>VA</label> | <label>VA</label> | ||||
<input type="radio" | <input type="radio" | ||||
name="loan_type" | name="loan_type" | ||||
value="4" | value="4" | ||||
:checked="loan.type.id == 4" | :checked="loan.type.id == 4" | ||||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||||
@change="(e) => $emit('update:loanType', 4)"> | |||||
<label>USDA</label> | <label>USDA</label> | ||||
</section> | </section> | ||||
@@ -105,7 +105,7 @@ | |||||
<label>Loan to Value (%)</label> | <label>Loan to Value (%)</label> | ||||
<input :value="loan.ltv" @input="(e) => $emit('update:ltv', e)"> | <input :value="loan.ltv" @input="(e) => $emit('update:ltv', e)"> | ||||
<label>Loan Amount ($)</label> | <label>Loan Amount ($)</label> | ||||
<input :value="loan.amount" | |||||
<input :value="loan.amount / 100" | |||||
@input="(e) => $emit('update:amount', e)"> | @input="(e) => $emit('update:amount', e)"> | ||||
<label>Housing Expense DTI (%) - Optional</label> | <label>Housing Expense DTI (%) - Optional</label> | ||||
<input :value="loan.housingDti" | <input :value="loan.housingDti" | ||||
@@ -36,7 +36,7 @@ class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 | |||||
@update:price="setPrice" | @update:price="setPrice" | ||||
@update:property="(p) => estimate.property = p" | @update:property="(p) => estimate.property = p" | ||||
@update:loanType="(lt) => loans[sel].type = lt" | |||||
@update:loanType="(lt) => loan.type.id = lt" | |||||
@update:term="(lt) => loans[sel].term = lt" | @update:term="(lt) => loans[sel].term = lt" | ||||
@update:program="(p) => loans[sel].program = p" | @update:program="(p) => loans[sel].program = p" | ||||
@update:ltv="setLtv" | @update:ltv="setLtv" | ||||
@@ -56,7 +56,7 @@ class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0 | |||||
@continue="generate" | @continue="generate" | ||||
/> | /> | ||||
<loan-summary v-if="hash == '#new/summary'" | <loan-summary v-if="hash == '#new/summary'" | ||||
:loan="loan" :downpayment="estimate.price - loan.amount"/> | |||||
:loan="loan" :downpayment="estimate.price - loan.amount" :token="token" :estimate="estimate"/> | |||||
</div> | </div> | ||||
</template> | </template> | ||||
@@ -66,10 +66,10 @@ import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js" | |||||
import LoanDetails from "./details.vue" | import LoanDetails from "./details.vue" | ||||
import LoanSummary from "./summary.vue" | import LoanSummary from "./summary.vue" | ||||
// The default values of a new estimate | |||||
// The default values of a new loan | |||||
const example = { | const example = { | ||||
title: "Example", | title: "Example", | ||||
type: "", | |||||
type: {}, | |||||
term: 0, | term: 0, | ||||
ltv: 0, // Loan to home value ratio | ltv: 0, // Loan to home value ratio | ||||
dti: 0, | dti: 0, | ||||
@@ -91,11 +91,7 @@ const example = { | |||||
// The default loans on a new estimate | // The default loans on a new estimate | ||||
const loans = [ | const loans = [ | ||||
Object.assign({}, example,), | |||||
Object.assign( | |||||
Object.assign({}, example), | |||||
{title: "Another One", mi: {rate: 0}, type: {}} | |||||
), | |||||
Object.assign({mi: { monthly: false, rate: 0 }, type: {}}, example) | |||||
] | ] | ||||
// Default estimate fields | // Default estimate fields | ||||
@@ -115,7 +111,7 @@ function loan() { | |||||
function create() { | function create() { | ||||
this.estimate.loans.push( | this.estimate.loans.push( | ||||
Object.assign( | Object.assign( | ||||
{}, | |||||
{mi: { monthly: false, rate: 0 }, type: {}}, | |||||
example, {fees: this.createFees()} | example, {fees: this.createFees()} | ||||
) | ) | ||||
) | ) | ||||
@@ -155,10 +151,11 @@ function setLtv(e) { | |||||
this.loan.ltv = ltv | this.loan.ltv = ltv | ||||
let num = ltv / 100 * this.estimate.price | let num = ltv / 100 * this.estimate.price | ||||
this.loan.amount = Math.round(num*100) / 100 | |||||
this.loan.amount = Math.round(num*100) | |||||
} | } | ||||
// Changes loan.amount\'s <input> and data() values, then syncs with data.ltv | // Changes loan.amount\'s <input> and data() values, then syncs with data.ltv | ||||
// Loan amount is in cents but LTV is in decimals so some rounding needs to be done. | |||||
function setAmount(e) { | function setAmount(e) { | ||||
let amount = strip(e) | let amount = strip(e) | ||||
if (!this.estimate.price) return | if (!this.estimate.price) return | ||||
@@ -166,7 +163,7 @@ function setAmount(e) { | |||||
if (amount > this.loan.price) amount = this.loan.price | if (amount > this.loan.price) amount = this.loan.price | ||||
if (amount < 0) amount = 0 | if (amount < 0) amount = 0 | ||||
this.loan.amount = amount | |||||
this.loan.amount = Math.round(amount * 100) | |||||
let num = amount / this.estimate.price * 100 | let num = amount / this.estimate.price * 100 | ||||
this.loan.ltv = Math.round(num*100) / 100 | this.loan.ltv = Math.round(num*100) / 100 | ||||
} | } | ||||
@@ -29,8 +29,8 @@ | |||||
</template> | </template> | ||||
<script setup> | <script setup> | ||||
import { ref, computed } from 'vue' | |||||
const props = defineProps(['downpayment', 'loan', 'valid']) | |||||
import { ref, computed, onMounted } from 'vue' | |||||
const props = defineProps(['downpayment', 'loan', 'valid', 'token', 'estimate']) | |||||
function amortize(principle, rate, periods) { | function amortize(principle, rate, periods) { | ||||
return principle * rate*(1+rate)**periods / ((1+rate)**periods - 1) | return principle * rate*(1+rate)**periods / ((1+rate)**periods - 1) | ||||
@@ -65,6 +65,7 @@ const fees = computed(() => { | |||||
}, 0 | }, 0 | ||||
).toFixed(2) | ).toFixed(2) | ||||
}) | }) | ||||
const credits = computed(() => { | const credits = computed(() => { | ||||
return props.loan.fees.reduce((total, x) => { | return props.loan.fees.reduce((total, x) => { | ||||
return x.amount < 0 ? total + x.amount : 0 | return x.amount < 0 ? total + x.amount : 0 | ||||
@@ -72,8 +73,29 @@ const credits = computed(() => { | |||||
).toFixed(2) | ).toFixed(2) | ||||
}) | }) | ||||
const cashToClose = computed(() => { | const cashToClose = computed(() => { | ||||
return fees + credits + downpayment | return fees + credits + downpayment | ||||
}) | }) | ||||
function validate() { | |||||
fetch(`/api/estimate/validate`, | |||||
{method: 'POST', | |||||
body: JSON.stringify(props.estimate), | |||||
headers: { | |||||
"Accept": "application/json", | |||||
"Authorization": `Bearer ${props.token}`, | |||||
}, | |||||
}).then(resp => { | |||||
if (resp.ok && resp.status == 200) { | |||||
return | |||||
} else { | |||||
// resp.text().then(t => this.errors = [t]) | |||||
window.location.hash = 'new' | |||||
} | |||||
}) | |||||
} | |||||
onMounted(() => {validate()}) | |||||
</script> | </script> |
@@ -87,7 +87,7 @@ type LoanType struct { | |||||
type Loan struct { | type Loan struct { | ||||
Id int `json:id` | Id int `json:id` | ||||
EstimateId int `json:estimate_id` | EstimateId int `json:estimate_id` | ||||
Type LoanType `json:"loanType"` | |||||
Type LoanType `json:"type"` | |||||
Amount int `json:"amount"` | Amount int `json:"amount"` | ||||
Amortization string `json:"amortization"` | Amortization string `json:"amortization"` | ||||
Term int `json:"term"` | Term int `json:"term"` | ||||
@@ -954,15 +954,17 @@ func checkEstimate(e Estimate) error { | |||||
// Can be used to check rules for specific loan types | // Can be used to check rules for specific loan types | ||||
var err error | var err error | ||||
switch l.Type.Name { | |||||
case "Conventional": | |||||
switch l.Type.Id { | |||||
case 1: | |||||
err = checkConventional(l, e.Borrower) | err = checkConventional(l, e.Borrower) | ||||
case "FHA": | |||||
case 2: | |||||
err = checkFHA(l, e.Borrower) | err = checkFHA(l, e.Borrower) | ||||
case "VA": | |||||
case 3: | |||||
err = checkConventional(l, e.Borrower) | err = checkConventional(l, e.Borrower) | ||||
case "USDA": | |||||
case 4: | |||||
err = checkConventional(l, e.Borrower) | err = checkConventional(l, e.Borrower) | ||||
default: | |||||
err = errors.New("Invalid loan type") | |||||
} | } | ||||
if err != nil { return err } | if err != nil { return err } | ||||
@@ -976,7 +978,7 @@ func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||||
var estimate Estimate | var estimate Estimate | ||||
err := json.NewDecoder(r.Body).Decode(&estimate) | err := json.NewDecoder(r.Body).Decode(&estimate) | ||||
if err != nil { http.Error(w, "Invalid fields.", 422); return } | |||||
if err != nil { http.Error(w, err.Error(), 422); return } | |||||
err = checkEstimate(estimate) | err = checkEstimate(estimate) | ||||
if err != nil { http.Error(w, err.Error(), 406); return } | if err != nil { http.Error(w, err.Error(), 406); return } | ||||
@@ -1041,6 +1043,8 @@ func api(w http.ResponseWriter, r *http.Request) { | |||||
r.Method == http.MethodPost && | r.Method == http.MethodPost && | ||||
guard(r, 1): | guard(r, 1): | ||||
validateEstimate(w, db, r) | validateEstimate(w, db, r) | ||||
default: | |||||
http.Error(w, "Invalid route or token", 404) | |||||
} | } | ||||
db.Close() | db.Close() | ||||