Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Nelze vybrat více než 25 témat Téma musí začínat písmenem nebo číslem, může obsahovat pomlčky („-“) a může být dlouhé až 35 znaků.
 
 
 
 
 
 

250 řádky
6.2 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 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) => loans[sel].interestDays = d"
  43. @update:hazardEscrow="(h) => loans[sel].hazardEscrow = h"
  44. @update:hazard="(h) => loan.hazard = Math.round(h * 100)"
  45. @update:taxEscrow="(t) => 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. "() => loans[sel].mi.monthly = !loans[sel].mi.monthly"
  50. @continue="generate"
  51. />
  52. <loan-summary v-if="hash == '#new/summary'"
  53. :loan="loan" :downpayment="estimate.price - loan.amount" :token="token" :estimate="estimate"/>
  54. </div>
  55. </template>
  56. <script>
  57. import { stripLetters, strip, stripInt, stripPerc } from "../../helpers.js"
  58. import LoanDetails from "./details.vue"
  59. import LoanSummary from "./summary.vue"
  60. // The default values of a new loan
  61. const example = {
  62. title: "Example",
  63. type: {},
  64. term: 0,
  65. ltv: 0, // Loan to home value ratio
  66. dti: 0,
  67. housingDti: 0,
  68. amount: 0,
  69. interest: 0,
  70. interestDays: 0,
  71. hazard: 0, // Hazard insurance monthly payment
  72. hazardEscrow: 0, // Hazard insurance escrow in months (0 is none)
  73. tax: 0, // Real Estate taxes monthly payment
  74. taxEscrow: 0, // Months to escrow (0 is none)
  75. hoi: 10000, // Home owner's association monthly fee
  76. program: "",
  77. pud: true, // Property under development
  78. zip: '',
  79. fees: [],
  80. mi: { monthly: false, rate: 0 }
  81. }
  82. // The default loans on a new estimate
  83. const loans = [
  84. Object.assign({mi: { monthly: false, rate: 0 }, type: {}}, example)
  85. ]
  86. // Default estimate fields
  87. const estimate = {
  88. property: "",
  89. transaction: "",
  90. occupancy: "",
  91. price: 0,
  92. borrower: {num: 0, credit: 0, income: 0},
  93. loans: loans,
  94. }
  95. function loan() {
  96. return this.estimate.loans[this.sel]
  97. }
  98. // Clone loan from initial example as a new loan
  99. function create() {
  100. this.estimate.loans.push(
  101. Object.assign(
  102. {mi: { monthly: false, rate: 0 }, type: {}},
  103. example, {fees: this.createFees()}
  104. )
  105. )
  106. }
  107. function createFees() {
  108. return this.fees.map(f => Object.assign({}, f))
  109. }
  110. function del() {
  111. if (this.loans.length > 1) {
  112. let x = this.sel
  113. this.sel = 0
  114. this.loans.splice(x, 1)
  115. }
  116. }
  117. // Updates the property price for all loans and their fee amounts.
  118. function setPrice(value) {
  119. this.estimate.price = Math.round(value*100)
  120. this.estimate.loans[this.sel].fees.forEach(fee => {
  121. if (fee.perc) fee.amount = Math.round(fee.perc * value)
  122. })
  123. this.estimate.loans.forEach(l => {l.ltv = 0; l.amount = 0})
  124. }
  125. // Changes loan.ltv's <input> and data() values, then syncs with data.amount
  126. function setLtv(e) {
  127. let ltv = strip(e)
  128. if (!this.estimate.price) return
  129. if (ltv > 100) ltv = 100
  130. if (ltv < 0) ltv = 0
  131. this.loan.ltv = ltv
  132. let num = ltv / 100 * this.estimate.price
  133. this.loan.amount = Math.round(num)
  134. }
  135. // Changes loan.amount\'s <input> and data() values, then syncs with data.ltv
  136. // Loan amount is in cents but LTV is in decimals so some rounding needs to be done.
  137. function setAmount(e) {
  138. let amount = strip(e) * 100
  139. if (!this.estimate.price) return
  140. if (amount > this.estimate.price) amount = this.estimate.price
  141. if (amount < 0) amount = 0
  142. this.loan.amount = Math.round(amount)
  143. let num = amount / this.estimate.price
  144. this.loan.ltv = Math.round(num*100)
  145. }
  146. function setDti(e) {
  147. let dti = strip(e)
  148. let loan = this.loans[this.sel]
  149. if (!loan.price) return
  150. if (dti > 100) dti = 100
  151. if (dti < 0) dti = 0
  152. e.target.value = dti
  153. loan.dti = dti
  154. }
  155. function setHousingDti(e) {
  156. let housingDti = strip(e)
  157. let loan = this.loans[this.sel]
  158. if (!loan.price) return
  159. if (housingDti > 100) housingDti = 100
  160. if (housingDti < 0) housingDti = 0
  161. e.target.value = housingDti
  162. loan.housingDti = housingDti
  163. }
  164. function changeLoanType(id) {
  165. if (id == this.loan.type.id) return
  166. // Set mandatory upfront MIP in fees if type is FHA
  167. let i = this.loan.fees.findIndex(
  168. l => l.required && l.name == "FHA Upfront MIP"
  169. )
  170. if (id == 2) {
  171. this.loan.fees.push({
  172. amount: Math.round(this.estimate.price*1.75/100),
  173. perc: 1.75,
  174. name: "FHA Upfront MIP",
  175. type: "Required",
  176. id: 0,
  177. required: true
  178. })
  179. } else if (i >= 0) {
  180. this.loan.fees.splice(i, 1)
  181. }
  182. this.loan.type.id = id
  183. }
  184. function generate() {
  185. window.location.hash = 'new/summary'
  186. }
  187. // Percentage values of fees always take precedent over amounts. The conversion
  188. // happens in setPrice()
  189. export default {
  190. components: { LoanSummary, LoanDetails },
  191. methods: {
  192. generate, createFees, del, create, setPrice, setLtv, setAmount,
  193. setDti, setHousingDti, changeLoanType
  194. },
  195. computed: { loan },
  196. props: ['user', 'fees', 'token'],
  197. data() {
  198. return {
  199. estimate: estimate,
  200. loans: estimate.loans,
  201. sel: 0,
  202. errors: [],
  203. hash: window.location.hash
  204. }
  205. },
  206. created() {
  207. this.estimate.loans.forEach(l => l.fees = this.createFees())
  208. window.addEventListener("hashchange",
  209. () => this.hash = window.location.hash)
  210. }
  211. }
  212. </script>