Skouter mortgage estimates. Web application with view written in PHP and Vue, but controller and models in Go.
Du kannst nicht mehr als 25 Themen auswählen Themen müssen entweder mit einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

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