From 27de7ea4164ab069d96ced016882598dc14b1a78 Mon Sep 17 00:00:00 2001 From: Immanuel Onyeka Date: Fri, 8 Dec 2023 19:24:09 -0500 Subject: [PATCH] Allow users to delete saved estimates --- components/estimates.vue | 25 ++++ migrations/0_29092022_setup_tables.sql | 39 ++++-- migrations/reset.sql | 3 +- skouter.go | 172 ++++++++++++++++++++----- 4 files changed, 195 insertions(+), 44 deletions(-) diff --git a/components/estimates.vue b/components/estimates.vue index d1de280..a2ed294 100644 --- a/components/estimates.vue +++ b/components/estimates.vue @@ -55,6 +55,7 @@ ${{(estimate.price / 100).toLocaleString()}} + @@ -64,6 +65,11 @@ ${{(estimate.price / 100).toLocaleString()}} :fileName="`estimate-${estimate.id}.pdf`" :url="dlink"> + +

Are you sure you want to delete this estimate?

+ +
+ @@ -71,6 +77,7 @@ ${{(estimate.price / 100).toLocaleString()}} import { ref, computed, onMounted } from 'vue' import FeeDialog from "./fee-dialog.vue" import DDialog from "./download-dialog.vue" +import Dialog from "./dialog.vue" import { format } from "../helpers.js" const props = defineProps(['user', 'fees', 'token']) @@ -79,6 +86,7 @@ let edit = ref(null) let estimates = ref([]) let estimate = ref() let dlink = ref("") +let deleting = ref() function newFee(fee, isDebit) { if (!isDebit) { @@ -142,6 +150,23 @@ function getEstimates() { }) } +function del(e) { + fetch(`/api/estimate`, + {method: 'DELETE', + body: JSON.stringify(e), + headers: { + "Accept": "application/json", + "Authorization": `Bearer ${props.token}`, + }, + }).then(response => { + if (response.ok) { + estimates.value = estimates.value.filter(e => e.id != estimate.value.id) + estimate.value = null + deleting.value = false + } + }) +} + function download(estimate) { fetch(`/api/pdf`, {method: 'POST', diff --git a/migrations/0_29092022_setup_tables.sql b/migrations/0_29092022_setup_tables.sql index 945a4a4..7009ba9 100644 --- a/migrations/0_29092022_setup_tables.sql +++ b/migrations/0_29092022_setup_tables.sql @@ -78,18 +78,9 @@ CREATE TABLE loan_type ( PRIMARY KEY (`id`) ); -CREATE TABLE borrower ( - id INT AUTO_INCREMENT, - credit_score SMALLINT NOT NULL, - monthly_income INT NOT NULL, - num TINYINT NOT NULL, /* Number of people borrowing. */ - PRIMARY KEY (`id`) -); - CREATE TABLE estimate ( id INT AUTO_INCREMENT, user_id INT NOT NULL, - borrower_id INT NOT NULL, transaction ENUM('Purchase', 'Refinance'), price INT NOT NULL, property ENUM('Single Detached', @@ -99,10 +90,31 @@ CREATE TABLE estimate ( occupancy ENUM('Primary', 'Secondary', 'Investment'), zip VARCHAR(10), pud BOOLEAN, /* Property under development */ + PRIMARY KEY (`id`) +); + +CREATE TABLE borrower ( + id INT AUTO_INCREMENT, + credit_score SMALLINT NOT NULL, + monthly_income INT NOT NULL, + num TINYINT NOT NULL, /* Number of people borrowing. */ + estimate_id INT UNIQUE NOT NULL, PRIMARY KEY (`id`), - FOREIGN KEY (borrower_id) REFERENCES borrower(id) + FOREIGN KEY (estimate_id) REFERENCES estimate(id) + ON DELETE CASCADE ); +CREATE TABLE estimate_template ( + id INT AUTO_INCREMENT, + estimate_id INT UNIQUE NOT NULL, + user_id INT NOT NULL, /* User who created the template */ + branch_id INT NOT NULL DEFAULT 0, +/* Specific branch allowed to use it. 0 is only user. */ + PRIMARY KEY (`id`), + FOREIGN KEY (estimate_id) REFERENCES estimate(id) +); + + CREATE TABLE loan ( id INT AUTO_INCREMENT, estimate_id INT NOT NULL, @@ -117,8 +129,9 @@ CREATE TABLE loan ( tax INT DEFAULT 0, /* Real estate taxes */ name VARCHAR(30) DEFAULT '', PRIMARY KEY (`id`), - FOREIGN KEY (estimate_id) REFERENCES estimate(id), + FOREIGN KEY (estimate_id) REFERENCES estimate(id) ON DELETE CASCADE, FOREIGN KEY (type_id) REFERENCES loan_type(id) + ON DELETE CASCADE ON UPDATE RESTRICT ); @@ -137,6 +150,7 @@ CREATE TABLE mi ( initial_amount INT DEFAULT 0, PRIMARY KEY (`id`), FOREIGN KEY (loan_id) REFERENCES loan(id) + ON DELETE CASCADE ); /* template = true fees are saved for users or branches. If template or default @@ -153,6 +167,7 @@ CREATE TABLE fee ( category VARCHAR(60), PRIMARY KEY (`id`), FOREIGN KEY (loan_id) REFERENCES loan(id) + ON DELETE CASCADE ); /* Templates to be reused by users or branches. Either user_id or branch_id must @@ -185,5 +200,5 @@ CREATE TABLE estimate_result ( total_credits INT NOT NULL, cash_to_close INT NOT NULL, PRIMARY KEY (`id`), - FOREIGN KEY (loan_id) REFERENCES loan(id) + FOREIGN KEY (loan_id) REFERENCES loan(id) ON DELETE CASCADE ); diff --git a/migrations/reset.sql b/migrations/reset.sql index 2d39b17..b268e1c 100644 --- a/migrations/reset.sql +++ b/migrations/reset.sql @@ -1,10 +1,11 @@ +DROP TABLE IF EXISTS estimate_template; DROP TABLE IF EXISTS estimate_result; DROP TABLE IF EXISTS mi; DROP TABLE IF EXISTS fee; DROP TABLE IF EXISTS fee_template; DROP TABLE IF EXISTS loan; -DROP TABLE IF EXISTS estimate; DROP TABLE IF EXISTS borrower; +DROP TABLE IF EXISTS estimate; DROP TABLE IF EXISTS loan_type; DROP TABLE IF EXISTS license; DROP TABLE IF EXISTS user; diff --git a/skouter.go b/skouter.go index 8f0ec03..b2bdf61 100644 --- a/skouter.go +++ b/skouter.go @@ -189,6 +189,13 @@ type Estimate struct { Loans []Loan `json:"loans"` } +type ETemplate struct { + Id int `json:"id"` + Estimate Estimate `json:"estimate"` + UserId int `json:"userId"` + BranchId int `json:"branchId"` +} + type Report struct { Title string Name string @@ -574,6 +581,19 @@ func deleteFeeTemp(w http.ResponseWriter, db *sql.DB, r *http.Request) { if err != nil { w.WriteHeader(500); return } } +func deleteEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { + var estimate Estimate + var err error + + err = json.NewDecoder(r.Body).Decode(&estimate) + if err != nil { w.WriteHeader(422); return } + + claims, err := getClaims(r) + err = estimate.del(db, claims.Id) + if err != nil { http.Error(w, err.Error(), 500); return } +} + + func getMi(db *sql.DB, loan int) (MI, error) { var mi MI @@ -608,25 +628,23 @@ func getMi(db *sql.DB, loan int) (MI, error) { return mi, nil } -func getBorrower(db *sql.DB, id int) (Borrower, error) { - var borrower Borrower +func (estimate *Estimate) getBorrower(db *sql.DB) error { + query := `SELECT id, credit_score, monthly_income, num FROM borrower + WHERE estimate_id = ? LIMIT 1` - row := db.QueryRow( - "SELECT * FROM borrower " + - "WHERE id = ? LIMIT 1", - id) + row := db.QueryRow(query, estimate.Id) if err := row.Scan( - &borrower.Id, - &borrower.Credit, - &borrower.Income, - &borrower.Num, + &estimate.Borrower.Id, + &estimate.Borrower.Credit, + &estimate.Borrower.Income, + &estimate.Borrower.Num, ) err != nil { - return borrower, fmt.Errorf("Borrower scanning error: %v", err) + return fmt.Errorf("Borrower scanning error: %v", err) } - return borrower, nil + return nil } // Query Lender APIs and parse responses into MI structs @@ -1740,6 +1758,14 @@ func getLoans(db *sql.DB, e int, id int) ( []Loan, error ) { return loans, nil } +func getEstimate(db *sql.DB, id int) ( Estimate, error ) { + estimates, err := getEstimates(db, id, 0) + + if err != nil { return Estimate{}, err } + + return estimates[0], nil +} + func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) { var estimates []Estimate var query string @@ -1749,7 +1775,6 @@ func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) { query = `SELECT id, user_id, - borrower_id, transaction, price, property, @@ -1773,7 +1798,6 @@ func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) { if err := rows.Scan( &estimate.Id, &estimate.User, - &estimate.Borrower.Id, &estimate.Transaction, &estimate.Price, &estimate.Property, @@ -1785,11 +1809,10 @@ func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) { return estimates, err } - borrower, err := getBorrower(db, estimate.Borrower.Id) + err := estimate.getBorrower(db) if err != nil { return estimates, err } - estimate.Borrower = borrower estimates = append(estimates, estimate) } @@ -1804,33 +1827,80 @@ func getEstimates(db *sql.DB, id int, user int) ( []Estimate, error ) { return estimates, nil } +func getETemplate(db *sql.DB, id int, user int) ( []ETemplate, error ) { + var eTemplates []ETemplate + var query string + var rows *sql.Rows + var err error + + query = `SELECT + id, + estimate_id, + user_id, + branch_id, + FROM estimate_template WHERE id = CASE @e := ? WHEN 0 THEN id ELSE @e END AND + user_id = CASE @e := ? WHEN 0 THEN user_id ELSE @e END + ` + rows, err = db.Query(query, id, user) + + if err != nil { + return eTemplates, err + } + + defer rows.Close() + + for rows.Next() { + var e ETemplate + + if err := rows.Scan( + &e.Id, + &e.Estimate.Id, + &e.UserId, + &e.BranchId, + ) + err != nil { + return eTemplates, err + } + + e.Estimate, err = getEstimate(db, e.Estimate.Id) + if err != nil { + return eTemplates, err + } + + eTemplates = append(eTemplates, e) + } + + return eTemplates, nil +} + // Accepts a borrower struct and returns the id of the inserted borrower and // any related error. -func insertBorrower(db *sql.DB, borrower Borrower) (int, error) { +func (estimate *Estimate) insertBorrower(db *sql.DB) error { var query string var row *sql.Row var err error - var id int // Inserted loan's id query = `INSERT INTO borrower ( + estimate_id, credit_score, monthly_income, num ) - VALUES (?, ?, ?) + VALUES (?, ?, ?, ?) RETURNING id ` row = db.QueryRow(query, - borrower.Credit, - borrower.Income, - borrower.Num, + estimate.Id, + estimate.Borrower.Credit, + estimate.Borrower.Income, + estimate.Borrower.Num, ) - err = row.Scan(&id) - if err != nil { return 0, err } + err = row.Scan(&estimate.Borrower.Id) + if err != nil { return err } - return id, nil + return nil } func insertMi(db *sql.DB, mi MI) (int, error) { @@ -1968,13 +2038,9 @@ func (estimate *Estimate) insertEstimate(db *sql.DB) (error){ var err error // var id int // Inserted estimate's id - estimate.Borrower.Id, err = insertBorrower(db, estimate.Borrower) - if err != nil { return err } - query = `INSERT INTO estimate ( user_id, - borrower_id, transaction, price, property, @@ -1982,12 +2048,11 @@ func (estimate *Estimate) insertEstimate(db *sql.DB) (error){ zip, pud ) - VALUES (?, ?, ?, ?, ?, ?, ?, ?) + VALUES (?, ?, ?, ?, ?, ?, ?) RETURNING id ` row = db.QueryRow(query, estimate.User, - estimate.Borrower.Id, estimate.Transaction, estimate.Price, estimate.Property, @@ -1998,6 +2063,9 @@ func (estimate *Estimate) insertEstimate(db *sql.DB) (error){ err = row.Scan(&estimate.Id) if err != nil { return err } + + err = estimate.insertBorrower(db) + if err != nil { return err } for i := range estimate.Loans { estimate.Loans[i].EstimateId = estimate.Id @@ -2008,6 +2076,44 @@ func (estimate *Estimate) insertEstimate(db *sql.DB) (error){ return nil } +func (estimate *Estimate) del(db *sql.DB, user int) (error) { + var query string + var err error + + query = `DELETE FROM estimate WHERE id = ? AND user_id = ?` + + _, err = db.Exec(query, estimate.Id, user) + if err != nil { return err } + + return nil +} + +func (eTemplate *ETemplate) insert(db *sql.DB) (error) { + var query string + var row *sql.Row + var err error + + query = `INSERT INTO estimate_template + ( + user_id, + branch_id, + estimate_id, + ) + VALUES (?, ?, ?) + RETURNING id + ` + row = db.QueryRow(query, + eTemplate.UserId, + eTemplate.BranchId, + eTemplate.Estimate.Id, + ) + + err = row.Scan(&eTemplate.Id) + if err != nil { return err } + + return nil +} + func createEstimate(w http.ResponseWriter, db *sql.DB, r *http.Request) { var estimate Estimate err := json.NewDecoder(r.Body).Decode(&estimate) @@ -2383,6 +2489,10 @@ func api(w http.ResponseWriter, r *http.Request) { r.Method == http.MethodPost && guard(r, 1): createEstimate(w, db, r) + case match(p, "/api/estimate", &args) && + r.Method == http.MethodDelete && + guard(r, 1): + deleteEstimate(w, db, r) case match(p, "/api/estimate/validate", &args) && r.Method == http.MethodPost && guard(r, 1):