@@ -66,26 +66,26 @@ | |||
name="loan_type" | |||
:checked="loan.type.id == 1" | |||
value="1" | |||
@change="(e) => $emit('update:loanType', e.target.value)" | |||
@change="(e) => $emit('update:loanType', 1)" | |||
> | |||
<label>Conventional</label> | |||
<input type="radio" | |||
name="loan_type" | |||
value="2" | |||
:checked="loan.type.id == 2" | |||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||
@change="(e) => $emit('update:loanType', 2)"> | |||
<label>FHA</label> | |||
<input type="radio" | |||
name="loan_type" | |||
value="3" | |||
:checked="loan.type.id == 3" | |||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||
@change="(e) => $emit('update:loanType', 3)"> | |||
<label>VA</label> | |||
<input type="radio" | |||
name="loan_type" | |||
value="4" | |||
:checked="loan.type.id == 4" | |||
@change="(e) => $emit('update:loanType', e.target.value)"> | |||
@change="(e) => $emit('update:loanType', 4)"> | |||
<label>USDA</label> | |||
</section> | |||
@@ -105,7 +105,7 @@ | |||
<label>Loan to Value (%)</label> | |||
<input :value="loan.ltv" @input="(e) => $emit('update:ltv', e)"> | |||
<label>Loan Amount ($)</label> | |||
<input :value="loan.amount" | |||
<input :value="loan.amount / 100" | |||
@input="(e) => $emit('update:amount', e)"> | |||
<label>Housing Expense DTI (%) - Optional</label> | |||
<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: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:program="(p) => loans[sel].program = p" | |||
@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" | |||
/> | |||
<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> | |||
</template> | |||
@@ -66,10 +66,10 @@ import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js" | |||
import LoanDetails from "./details.vue" | |||
import LoanSummary from "./summary.vue" | |||
// The default values of a new estimate | |||
// The default values of a new loan | |||
const example = { | |||
title: "Example", | |||
type: "", | |||
type: {}, | |||
term: 0, | |||
ltv: 0, // Loan to home value ratio | |||
dti: 0, | |||
@@ -91,11 +91,7 @@ const example = { | |||
// The default loans on a new estimate | |||
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 | |||
@@ -115,7 +111,7 @@ function loan() { | |||
function create() { | |||
this.estimate.loans.push( | |||
Object.assign( | |||
{}, | |||
{mi: { monthly: false, rate: 0 }, type: {}}, | |||
example, {fees: this.createFees()} | |||
) | |||
) | |||
@@ -155,10 +151,11 @@ function setLtv(e) { | |||
this.loan.ltv = ltv | |||
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 | |||
// Loan amount is in cents but LTV is in decimals so some rounding needs to be done. | |||
function setAmount(e) { | |||
let amount = strip(e) | |||
if (!this.estimate.price) return | |||
@@ -166,7 +163,7 @@ function setAmount(e) { | |||
if (amount > this.loan.price) amount = this.loan.price | |||
if (amount < 0) amount = 0 | |||
this.loan.amount = amount | |||
this.loan.amount = Math.round(amount * 100) | |||
let num = amount / this.estimate.price * 100 | |||
this.loan.ltv = Math.round(num*100) / 100 | |||
} | |||
@@ -29,8 +29,8 @@ | |||
</template> | |||
<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) { | |||
return principle * rate*(1+rate)**periods / ((1+rate)**periods - 1) | |||
@@ -65,6 +65,7 @@ const fees = computed(() => { | |||
}, 0 | |||
).toFixed(2) | |||
}) | |||
const credits = computed(() => { | |||
return props.loan.fees.reduce((total, x) => { | |||
return x.amount < 0 ? total + x.amount : 0 | |||
@@ -72,8 +73,29 @@ const credits = computed(() => { | |||
).toFixed(2) | |||
}) | |||
const cashToClose = computed(() => { | |||
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> |
@@ -87,7 +87,7 @@ type LoanType struct { | |||
type Loan struct { | |||
Id int `json:id` | |||
EstimateId int `json:estimate_id` | |||
Type LoanType `json:"loanType"` | |||
Type LoanType `json:"type"` | |||
Amount int `json:"amount"` | |||
Amortization string `json:"amortization"` | |||
Term int `json:"term"` | |||
@@ -954,15 +954,17 @@ func checkEstimate(e Estimate) error { | |||
// Can be used to check rules for specific loan types | |||
var err error | |||
switch l.Type.Name { | |||
case "Conventional": | |||
switch l.Type.Id { | |||
case 1: | |||
err = checkConventional(l, e.Borrower) | |||
case "FHA": | |||
case 2: | |||
err = checkFHA(l, e.Borrower) | |||
case "VA": | |||
case 3: | |||
err = checkConventional(l, e.Borrower) | |||
case "USDA": | |||
case 4: | |||
err = checkConventional(l, e.Borrower) | |||
default: | |||
err = errors.New("Invalid loan type") | |||
} | |||
if err != nil { return err } | |||
@@ -976,7 +978,7 @@ func validateEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { | |||
var estimate 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) | |||
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 && | |||
guard(r, 1): | |||
validateEstimate(w, db, r) | |||
default: | |||
http.Error(w, "Invalid route or token", 404) | |||
} | |||
db.Close() | |||