Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
25개 이상의 토픽을 선택하실 수 없습니다. Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

255 lines
6.4 KiB

  1. <template>
  2. <div id="new" class="page">
  3. <h2>New Loan</h2>
  4. <section class="loans-list">
  5. <h3 v-for="(l, indx) in estimate.loans"
  6. :class="sel == indx ? 'sel' : ''"
  7. @click="() => sel = indx"
  8. >
  9. {{l.title}}
  10. </h3>
  11. <div class="add">
  12. <svg @click="create"
  13. xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor"
  14. class="bi bi-plus" viewBox="0 0 16 16"> <path d="M8 4a.5.5 0 0 1 .5.5v3h3a.5.5 0
  15. 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>
  16. </div>
  17. </section>
  18. <loan-details v-if="hash == '#new'"
  19. :estimate="estimate"
  20. :loans="estimate.loans"
  21. :sel="sel"
  22. :loan="loan"
  23. :token="token"
  24. @update:name="(name) => loan.title = name"
  25. @del="del"
  26. @update:borrowerNum="(b) => estimate.borrower.num = b"
  27. @update:borrowerCredit="(c) => estimate.borrower.credit = c"
  28. @update:borrowerIncome="(m) => estimate.borrower.income = Math.round(m*100)"
  29. @update:transaction="(t) => estimate.transaction = t"
  30. @update:occupancy="(t) => estimate.occupancy = t"
  31. @update:price="setPrice"
  32. @update:property="(p) => estimate.property = p"
  33. @update:loanType="changeLoanType"
  34. @update:term="(lt) => loan.term = lt"
  35. @update:program="(p) => loan.program = p"
  36. @update:ltv="setLtv"
  37. @update:amount="setAmount"
  38. @update:housingDti="setHousingDti"
  39. @update:dti="setDti"
  40. @update:hoi="(hoi) => loan.hoi = Math.round(hoi*100)"
  41. @update:interest="(i) => loan.interest = i"
  42. @update:interestDays="(d) => estimate.loans[sel].interestDays = d"
  43. @update:hazardEscrow="(h) => estimate.loans[sel].hazardEscrow = h"
  44. @update:hazard="(h) => loan.hazard = Math.round(h * 100)"
  45. @update:taxEscrow="(t) => estimate.loans[sel].taxEscrow = t"
  46. @update:tax="(t) => loan.tax = Math.round(t*100)"
  47. @update:manualMI="perc => loan.mi.rate = perc"
  48. @toggle:manualMIMonthly=
  49. "() => estimate.loans[sel].mi.monthly = !estimate.loans[sel].mi.monthly"
  50. @continue="generate"
  51. />
  52. <loan-summary v-if="hash == '#new/summary'"
  53. :loan="loan"
  54. :downpayment="estimate.price - loan.amount"
  55. :token="token"
  56. :estimate="estimate"
  57. @update="(e) => estimate = e" />
  58. </div>
  59. </template>
  60. <script>
  61. import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js"
  62. import LoanDetails from "./details.vue"
  63. import LoanSummary from "./summary.vue"
  64. // The default values of a new loan
  65. const example = {
  66. title: "Example",
  67. type: {},
  68. term: 0,
  69. ltv: 0, // Loan to home value ratio
  70. dti: 0,
  71. housingDti: 0,
  72. amount: 0,
  73. interest: 0,
  74. interestDays: 0,
  75. hazard: 0, // Hazard insurance monthly payment
  76. hazardEscrow: 0, // Hazard insurance escrow in months (0 is none)
  77. tax: 0, // Real Estate taxes monthly payment
  78. taxEscrow: 0, // Months to escrow (0 is none)
  79. hoi: 10000, // Home owner's association monthly fee
  80. program: "",
  81. pud: true, // Property under development
  82. zip: '',
  83. fees: [],
  84. }
  85. // The default loans on a new estimate
  86. const loans = [
  87. Object.assign({mi: { monthly: false, rate: 0 }, type: {}}, example)
  88. ]
  89. // Default estimate fields
  90. let estimate = {
  91. property: "",
  92. transaction: "",
  93. occupancy: "",
  94. price: 0,
  95. borrower: {num: 0, credit: 0, income: 0},
  96. loans: loans,
  97. }
  98. function loan() {
  99. return this.estimate.loans[this.sel]
  100. }
  101. // Clone loan from initial example as a new loan
  102. function create() {
  103. this.estimate.loans.push(
  104. Object.assign(
  105. {mi: { monthly: false, rate: 0 }, type: {}},
  106. example, {fees: this.createFees()}
  107. )
  108. )
  109. // Move current loan to last inserted
  110. this.sel = this.estimate.loans.length - 1
  111. }
  112. function createFees() {
  113. return this.fees.map(f => Object.assign({}, f))
  114. }
  115. function del() {
  116. if (this.estimate.loans.length > 1) {
  117. let x = this.sel
  118. this.sel = 0
  119. this.estimate.loans.splice(x, 1)
  120. }
  121. }
  122. // Updates the property price for all loans and their fee amounts.
  123. function setPrice(value) {
  124. this.estimate.price = Math.round(value*100)
  125. this.estimate.loans[this.sel].fees.forEach(fee => {
  126. if (fee.perc) fee.amount = Math.round(fee.perc * value)
  127. })
  128. this.estimate.loans.forEach(l => {l.ltv = 0; l.amount = 0})
  129. }
  130. // Changes loan.ltv's <input> and data() values, then syncs with data.amount
  131. function setLtv(e) {
  132. let ltv = strip(e)
  133. if (!this.estimate.price) return
  134. if (ltv > 100) ltv = 100
  135. if (ltv < 0) ltv = 0
  136. this.loan.ltv = ltv
  137. let num = ltv / 100 * this.estimate.price
  138. this.loan.amount = Math.round(num)
  139. }
  140. // Changes loan.amount\'s <input> and data() values, then syncs with data.ltv
  141. // Loan amount is in cents but LTV is in decimals so some rounding needs to be done.
  142. function setAmount(e) {
  143. let amount = strip(e) * 100
  144. if (!this.estimate.price) return
  145. if (amount > this.estimate.price) amount = this.estimate.price
  146. if (amount < 0) amount = 0
  147. this.loan.amount = Math.round(amount)
  148. let num = amount / this.estimate.price
  149. this.loan.ltv = Math.round(num*100)
  150. }
  151. function setDti(e) {
  152. let dti = strip(e)
  153. let loan = this.estimate.loans[this.sel]
  154. if (!loan.price) return
  155. if (dti > 100) dti = 100
  156. if (dti < 0) dti = 0
  157. e.target.value = dti
  158. loan.dti = dti
  159. }
  160. function setHousingDti(e) {
  161. let housingDti = strip(e)
  162. let loan = this.estimate.loans[this.sel]
  163. if (!loan.price) return
  164. if (housingDti > 100) housingDti = 100
  165. if (housingDti < 0) housingDti = 0
  166. e.target.value = housingDti
  167. loan.housingDti = housingDti
  168. }
  169. function changeLoanType(id) {
  170. if (id == this.loan.type.id) return
  171. // Set mandatory upfront MIP in fees if type is FHA
  172. let i = this.loan.fees.findIndex(
  173. l => l.required && l.name == "FHA Upfront MIP"
  174. )
  175. if (id == 2) {
  176. this.loan.fees.push({
  177. amount: Math.round(this.estimate.price*1.75/100),
  178. perc: 1.75,
  179. name: "FHA Upfront MIP",
  180. type: "Required",
  181. id: 0,
  182. required: true
  183. })
  184. } else if (i >= 0) {
  185. this.loan.fees.splice(i, 1)
  186. }
  187. this.loan.type.id = id
  188. }
  189. function generate() {
  190. window.location.hash = 'new/summary'
  191. }
  192. // Percentage values of fees always take precedent over amounts. The conversion
  193. // happens in setPrice()
  194. export default {
  195. components: { LoanSummary, LoanDetails },
  196. methods: {
  197. generate, createFees, del, create, setPrice, setLtv, setAmount,
  198. setDti, setHousingDti, changeLoanType
  199. },
  200. computed: { loan },
  201. props: ['user', 'fees', 'token'],
  202. data() {
  203. return {
  204. estimate: estimate,
  205. sel: 0,
  206. errors: [],
  207. hash: window.location.hash
  208. }
  209. },
  210. created() {
  211. this.estimate.loans.forEach(l => l.fees = this.createFees())
  212. window.addEventListener("hashchange",
  213. () => this.hash = window.location.hash)
  214. }
  215. }
  216. </script>