|
1 | | -import { Application } from "models" |
2 | | -import { renderViewAsPdf, renderViewAsPromise } from "../../utils/express-handlebars-pdf-client" |
3 | | -import db from "@/db/db-client" |
| 1 | +import { Application } from "models"; |
| 2 | +import { renderViewAsPdf, renderViewAsPromise } from "../../utils/express-handlebars-pdf-client"; |
| 3 | +import db from "@/db/db-client"; |
4 | 4 |
|
5 | 5 | export default class ApplicationLetterService { |
6 | | - #applicationId: number |
7 | | - #fundingType: string |
8 | | - #applicationData: any |
9 | | - #format: string |
| 6 | + #applicationId: number; |
| 7 | + #fundingRequestId: number; |
| 8 | + #applicationData: any; |
| 9 | + #format: string; |
10 | 10 |
|
11 | 11 | constructor({ |
12 | 12 | applicationId, |
13 | | - fundingType, |
| 13 | + fundingRequestId, |
14 | 14 | format, |
15 | 15 | }: { |
16 | | - applicationId: number |
17 | | - fundingType: string |
18 | | - format: string |
| 16 | + applicationId: number; |
| 17 | + fundingRequestId: number; |
| 18 | + format: string; |
19 | 19 | }) { |
20 | | - this.#applicationId = applicationId |
21 | | - this.#fundingType = fundingType |
22 | | - this.#format = format |
| 20 | + this.#applicationId = applicationId; |
| 21 | + this.#fundingRequestId = fundingRequestId; |
| 22 | + this.#format = format; |
23 | 23 | } |
24 | 24 |
|
25 | | - async generateApprovalLetter(): Promise<Buffer | string> { |
26 | | - const data = await this.#getApplicationData() |
27 | | - data.title = "Application Approval Letter" |
| 25 | + async generateApprovalLetter(name:string, position:string): Promise<Buffer | string> { |
| 26 | + const data = await this.#getApplicationData(name, position); |
| 27 | + const fundingRequest = await this.#getFundingRequest(); |
| 28 | + data.title = "Application Approval Letter"; |
28 | 29 |
|
29 | 30 | if (this.#format === "pdf") { |
30 | | - return renderViewAsPdf( |
31 | | - `./templates/admin/application-letter/approval/${this.#fundingType}`, |
32 | | - data |
33 | | - ) |
| 31 | + return renderViewAsPdf(this.#getTemplatePathForRequestType("approval", fundingRequest.requestTypeId), data); |
34 | 32 | } |
35 | 33 |
|
36 | 34 | if (this.#format === "html") { |
37 | | - return renderViewAsPromise( |
38 | | - `./templates/admin/application-letter/approval/${this.#fundingType}`, |
39 | | - data |
40 | | - ) |
| 35 | + return renderViewAsPromise(this.#getTemplatePathForRequestType("approval", fundingRequest.requestTypeId), data); |
41 | 36 | } |
42 | 37 |
|
43 | | - return Promise.reject(new Error(`Invalid format: ${this.#format}`)) |
| 38 | + return Promise.reject(new Error(`Invalid format: ${this.#format}`)); |
44 | 39 | } |
45 | 40 |
|
46 | | - async generateRejectionLetter(): Promise<Buffer | string> { |
47 | | - const data = await this.#getApplicationData() |
48 | | - data.title = "Application Rejection Letter" |
| 41 | + async generateRejectionLetter(name:string, position:string): Promise<Buffer | string> { |
| 42 | + const data = await this.#getApplicationData(name, position); |
| 43 | + const fundingRequest = await this.#getFundingRequest(); |
| 44 | + data.title = "Application Rejection Letter"; |
49 | 45 |
|
50 | 46 | if (this.#format === "pdf") { |
51 | | - return renderViewAsPdf( |
52 | | - `./templates/admin/application-letter/rejection/${this.#fundingType}`, |
53 | | - data |
54 | | - ) |
| 47 | + return renderViewAsPdf(this.#getTemplatePathForRequestType("rejection", fundingRequest.requestTypeId), data); |
55 | 48 | } |
56 | 49 |
|
57 | 50 | if (this.#format === "html") { |
58 | | - return renderViewAsPromise( |
59 | | - `./templates/admin/application-letter/rejection/${this.#fundingType}`, |
60 | | - data |
61 | | - ) |
| 51 | + return renderViewAsPromise(this.#getTemplatePathForRequestType("rejection", fundingRequest.requestTypeId), data); |
62 | 52 | } |
63 | 53 |
|
64 | | - return Promise.reject(new Error(`Invalid format: ${this.#format}`)) |
| 54 | + return Promise.reject(new Error(`Invalid format: ${this.#format}`)); |
65 | 55 | } |
66 | 56 |
|
67 | 57 | //// |
68 | 58 | // See https://xkcd.com/1179/ -> https://en.wikipedia.org/wiki/ISO_8601 for date format |
69 | 59 | async buildApprovalLetterFileName() { |
70 | | - const data = await this.#getApplicationData() |
| 60 | + const data = await this.#getApplicationData("", ""); |
71 | 61 |
|
72 | | - const studentLastName = this.#applicationData.student.person.lastName |
| 62 | + const studentLastName = this.#applicationData.student.person.lastName; |
73 | 63 | if (!studentLastName) { |
74 | | - Promise.reject(new Error("No student last name")) |
| 64 | + Promise.reject(new Error("No student last name")); |
75 | 65 | } |
76 | 66 |
|
77 | | - const formattedData = new Date().toISOString().slice(0, 10) // YYYYY-MM-DD |
78 | | - return `Approval Letter, ${studentLastName}, ${formattedData}.${this.#format}` |
| 67 | + const formattedData = new Date().toISOString().slice(0, 10); // YYYYY-MM-DD |
| 68 | + return `Approval Letter, ${studentLastName}, ${formattedData}.${this.#format}`; |
79 | 69 | } |
80 | 70 |
|
81 | 71 | async buildRejectionLetterFileName() { |
82 | | - await this.#getApplicationData() |
| 72 | + await this.#getApplicationData("", ""); |
83 | 73 |
|
84 | | - const studentLastName = this.#applicationData.student.person.lastName |
| 74 | + const studentLastName = this.#applicationData.student.person.lastName; |
85 | 75 | if (!studentLastName) { |
86 | | - Promise.reject(new Error("No student last name")) |
| 76 | + Promise.reject(new Error("No student last name")); |
87 | 77 | } |
88 | 78 |
|
89 | | - const formattedData = new Date().toISOString().slice(0, 10) // YYYYY-MM-DD |
90 | | - return `Rejection Letter, ${studentLastName}, ${formattedData}.pdf` |
| 79 | + const formattedData = new Date().toISOString().slice(0, 10); // YYYYY-MM-DD |
| 80 | + return `Rejection Letter, ${studentLastName}, ${formattedData}.pdf`; |
91 | 81 | } |
92 | 82 |
|
93 | 83 | // Private Methods |
94 | | - async #getApplicationData(): Promise<any> { |
95 | | - if (this.#applicationData) return this.#applicationData |
| 84 | + async #getApplicationData(name:string, position:string): Promise<any> { |
| 85 | + if (this.#applicationData) return this.#applicationData; |
96 | 86 |
|
97 | | - const application = await db("application").where({ id: this.#applicationId }).first() |
| 87 | + const application = await db("application").where({ id: this.#applicationId }).first(); |
98 | 88 | if (!application) { |
99 | | - return Promise.reject(new Error("Application not found")) |
| 89 | + return Promise.reject(new Error("Application not found")); |
100 | 90 | } |
101 | 91 |
|
102 | | - const student = await db("student").where({ id: application.studentId }).first() |
| 92 | + const student = await db("student").where({ id: application.studentId }).first(); |
103 | 93 | if (!student) { |
104 | | - return Promise.reject(new Error("Student not found")) |
| 94 | + return Promise.reject(new Error("Student not found")); |
105 | 95 | } |
106 | 96 |
|
107 | | - const person = await db("person").where({ id: student.personId }).first() |
| 97 | + const person = await db("person").where({ id: student.personId }).first(); |
108 | 98 | if (!person) { |
109 | | - return Promise.reject(new Error("Person not found")) |
| 99 | + return Promise.reject(new Error("Person not found")); |
110 | 100 | } |
111 | 101 |
|
112 | | - student.person = person |
113 | | - application.student = student |
| 102 | + const address = await db("person_address") |
| 103 | + .leftOuterJoin("city", "city.id", "person_address.city_id") |
| 104 | + .leftOuterJoin("province", "province.id", "person_address.province_id") |
| 105 | + .leftOuterJoin("country", "country.id", "person_address.country_id") |
| 106 | + .where({ "person_address.id": application.primaryAddressId }) |
| 107 | + .select([ |
| 108 | + "address1", |
| 109 | + "address2", |
| 110 | + "postalCode", |
| 111 | + "city.description as cityName", |
| 112 | + "province.description as provinceName", |
| 113 | + "country.description as countryName", |
| 114 | + ]) |
| 115 | + .first(); |
| 116 | + if (!address) { |
| 117 | + return Promise.reject(new Error("Address not found")); |
| 118 | + } |
| 119 | + |
| 120 | + const assessment = await db("assessment").where({ funding_request_id: this.#fundingRequestId }).first(); |
| 121 | + if (!assessment) { |
| 122 | + return Promise.reject(new Error("Assessment not found")); |
| 123 | + } |
| 124 | + |
| 125 | + const disbursementList = await db("disbursement").where({ funding_request_id: this.#fundingRequestId }).first(); |
| 126 | + if (!assessment) { |
| 127 | + return Promise.reject(new Error("Assessment not found")); |
| 128 | + } |
| 129 | + |
| 130 | + const institution = await db("institution") |
| 131 | + .innerJoin("institution_campus", "institution.id", "institution_campus.institution_id") |
| 132 | + .where({ "institution_campus.id": application.institutionCampusId }) |
| 133 | + .select("institution.name") |
| 134 | + .select("institution_campus.name as campusName") |
| 135 | + .first(); |
| 136 | + if (!institution) { |
| 137 | + return Promise.reject(new Error("Institution not found")); |
| 138 | + } |
| 139 | + |
| 140 | + const program = await db("study_area").where({ id: application.studyAreaId }).first(); |
| 141 | + if (!program) { |
| 142 | + return Promise.reject(new Error("Progrm not found")); |
| 143 | + } |
| 144 | + |
| 145 | + student.person = person; |
| 146 | + application.student = student; |
| 147 | + application.institution = institution; |
| 148 | + application.primaryAddress = address; |
114 | 149 |
|
115 | | - this.#applicationData = application |
| 150 | + this.#applicationData = application; |
| 151 | + |
| 152 | + let disbursements = []; |
| 153 | + if (disbursementList) { |
| 154 | + disbursements = disbursementList.map((d: any) => { |
| 155 | + return { amountInCents: d.paidAmount * 100, releaseDate: d.issueDate }; |
| 156 | + }); |
| 157 | + } |
116 | 158 | // return this.#applicationData |
117 | 159 | // TODO: replace dummy data with real data |
118 | 160 | return Promise.resolve({ |
119 | 161 | currentDate: new Date(), |
120 | 162 | // Example content |
121 | 163 | recipient: { |
122 | | - firstName: "James", |
123 | | - initials: "A.", |
124 | | - lastName: "Thompson", |
125 | | - address: "567 Oak Avenue", |
126 | | - city: "Metropolis", |
127 | | - province: "Quebec", |
128 | | - country: "Canada", |
129 | | - postalCode: "D4E 5F6", |
| 164 | + firstName: person.firstName, |
| 165 | + initials: person.initials, |
| 166 | + lastName: person.lastName, |
| 167 | + address: address.address1, |
| 168 | + city: address.cityName, |
| 169 | + province: address.provinceName, |
| 170 | + country: address.countryName, |
| 171 | + postalCode: address.postalCode, |
130 | 172 | }, |
131 | 173 | program: { |
132 | | - name: "Software Engineering Bootcamp", |
133 | | - startDate: new Date("2023-09-01"), |
134 | | - endDate: new Date("2024-03-01"), |
135 | | - institutionName: "Tech Academy", |
136 | | - ratePerWeekInCents: 400000, |
137 | | - approvalWeeks: 26, |
138 | | - travelAndAirFairCostInCents: 1200000, |
| 174 | + name: program.description, |
| 175 | + startDate: assessment.classesStartDate, |
| 176 | + endDate: assessment.classesEndDate, |
| 177 | + institutionName: |
| 178 | + application.institution.name + |
| 179 | + (application.institution.campusName == "Primary" ? "" : ` - ${application.institution.campusName}`), |
| 180 | + ratePerWeekInCents: assessment.weeklyAmount * 100, |
| 181 | + approvalWeeks: assessment.weeksAllowed, |
| 182 | + travelAndAirFairCostInCents: (assessment.airfareAmount + assessment.travelAllowance) * 100, |
139 | 183 | }, |
140 | | - disembursements: [ |
141 | | - { amountInCents: 1000000, releaseDate: new Date("2023-09-15") }, |
142 | | - { amountInCents: 1500000, releaseDate: new Date("2023-12-15") }, |
143 | | - ], |
| 184 | + disbursements, |
144 | 185 | studentFinancialAssistanceOfficer: { |
145 | | - firstName: "Samantha", |
146 | | - lastName: "Smith", |
| 186 | + name, |
| 187 | + position |
147 | 188 | }, |
148 | | - }) |
| 189 | + }); |
| 190 | + } |
| 191 | + |
| 192 | + async #getFundingRequest(): Promise<any> { |
| 193 | + const fundingRequest = await db("funding_request").where({ id: this.#fundingRequestId }).first(); |
| 194 | + if (!fundingRequest) { |
| 195 | + return Promise.reject(new Error("Funding request not found")); |
| 196 | + } |
| 197 | + return Promise.resolve(fundingRequest); |
| 198 | + } |
| 199 | + |
| 200 | + #getTemplatePathForRequestType(outcome: string, typeId: number): string { |
| 201 | + let base = `./templates/admin/application-letter/${outcome}`; |
| 202 | + switch (typeId) { |
| 203 | + case 2: |
| 204 | + return `${base}/yukon-grant-student`; |
| 205 | + default: |
| 206 | + return `${base}/test`; |
| 207 | + } |
149 | 208 | } |
150 | 209 | } |
0 commit comments