Skip to content

Commit 7cf09b2

Browse files
authored
Merge pull request #88 from icefoganalytics/test
Aug 11 Updates
2 parents 18599ca + 89711c4 commit 7cf09b2

File tree

10 files changed

+609
-545
lines changed

10 files changed

+609
-545
lines changed

src/api/routes/admin/application-letter-router.ts

Lines changed: 16 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,19 @@ import ApplicationLetterService from "../../services/admin/application-letter-se
66
export const applicationLetterRouter = express.Router()
77

88
applicationLetterRouter.get(
9-
"/:applicationId/approval/:fundingType([^./]+).?:format?",
9+
"/:applicationId/approval/:fundingRequestId/:format?",
1010
[param("id").isInt().notEmpty()],
1111
async (req: Request, res: Response) => {
1212
const applicationId = parseInt(req.params.applicationId)
13-
const fundingType = req.params.fundingType
13+
const fundingRequestId = parseInt(req.params.fundingRequestId)
1414
const format = req.params.format || "pdf"
1515

16+
let officerName = `${req.user.first_name} ${req.user.last_name}`
17+
let officerPosition = req.user.position
18+
1619
try {
17-
const applicationLetterService = new ApplicationLetterService({ applicationId, fundingType, format })
18-
const approvalLetter = await applicationLetterService.generateApprovalLetter()
20+
const applicationLetterService = new ApplicationLetterService({ applicationId, fundingRequestId, format })
21+
const approvalLetter = await applicationLetterService.generateApprovalLetter(officerName, officerPosition)
1922
if (format === "pdf") {
2023
const fileName = await applicationLetterService.buildApprovalLetterFileName()
2124
res.setHeader("Content-disposition", `attachment; filename="${fileName}"`)
@@ -36,7 +39,7 @@ applicationLetterRouter.get(
3639
res.status(404).send({
3740
statusCode: 404,
3841
status: "Not Found",
39-
message: `Could not find application letter with id "${applicationId}" and funding type "${fundingType}".`,
42+
message: `Could not find application letter with id "${applicationId}" and funding request "${fundingRequestId}".`,
4043
})
4144
} else {
4245
res.status(422).send({
@@ -57,16 +60,19 @@ applicationLetterRouter.get(
5760
)
5861

5962
applicationLetterRouter.get(
60-
"/:applicationId/rejection/:fundingType([^./]+).?:format?",
63+
"/:applicationId/rejection/:fundingRequestId/:format?",
6164
[param("id").isInt().notEmpty()],
6265
async (req: Request, res: Response) => {
6366
const applicationId = parseInt(req.params.applicationId)
64-
const fundingType = req.params.fundingType
67+
const fundingRequestId = parseInt(req.params.fundingRequestId)
6568
const format = req.params.format || "pdf"
6669

70+
let officerName = `${req.user.first_name} ${req.user.last_name}`
71+
let officerPosition = req.user.position
72+
6773
try {
68-
const applicationLetterService = new ApplicationLetterService({ applicationId, fundingType, format })
69-
const rejectionLetter = await applicationLetterService.generateRejectionLetter()
74+
const applicationLetterService = new ApplicationLetterService({ applicationId, fundingRequestId, format })
75+
const rejectionLetter = await applicationLetterService.generateRejectionLetter(officerName, officerPosition)
7076
if (format === "pdf") {
7177
const fileName = await applicationLetterService.buildRejectionLetterFileName()
7278
res.setHeader("Content-disposition", `attachment; filename="${fileName}"`)
@@ -87,7 +93,7 @@ applicationLetterRouter.get(
8793
res.status(404).send({
8894
statusCode: 404,
8995
status: "Not Found",
90-
message: `Could not find application letter with id "${applicationId}" and funding type "${fundingType}".`,
96+
message: `Could not find application letter with id "${applicationId}" and funding request "${fundingRequestId}".`,
9197
})
9298
} else {
9399
res.status(422).send({
Lines changed: 140 additions & 81 deletions
Original file line numberDiff line numberDiff line change
@@ -1,150 +1,209 @@
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";
44

55
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;
1010

1111
constructor({
1212
applicationId,
13-
fundingType,
13+
fundingRequestId,
1414
format,
1515
}: {
16-
applicationId: number
17-
fundingType: string
18-
format: string
16+
applicationId: number;
17+
fundingRequestId: number;
18+
format: string;
1919
}) {
20-
this.#applicationId = applicationId
21-
this.#fundingType = fundingType
22-
this.#format = format
20+
this.#applicationId = applicationId;
21+
this.#fundingRequestId = fundingRequestId;
22+
this.#format = format;
2323
}
2424

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";
2829

2930
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);
3432
}
3533

3634
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);
4136
}
4237

43-
return Promise.reject(new Error(`Invalid format: ${this.#format}`))
38+
return Promise.reject(new Error(`Invalid format: ${this.#format}`));
4439
}
4540

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";
4945

5046
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);
5548
}
5649

5750
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);
6252
}
6353

64-
return Promise.reject(new Error(`Invalid format: ${this.#format}`))
54+
return Promise.reject(new Error(`Invalid format: ${this.#format}`));
6555
}
6656

6757
////
6858
// See https://xkcd.com/1179/ -> https://en.wikipedia.org/wiki/ISO_8601 for date format
6959
async buildApprovalLetterFileName() {
70-
const data = await this.#getApplicationData()
60+
const data = await this.#getApplicationData("", "");
7161

72-
const studentLastName = this.#applicationData.student.person.lastName
62+
const studentLastName = this.#applicationData.student.person.lastName;
7363
if (!studentLastName) {
74-
Promise.reject(new Error("No student last name"))
64+
Promise.reject(new Error("No student last name"));
7565
}
7666

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}`;
7969
}
8070

8171
async buildRejectionLetterFileName() {
82-
await this.#getApplicationData()
72+
await this.#getApplicationData("", "");
8373

84-
const studentLastName = this.#applicationData.student.person.lastName
74+
const studentLastName = this.#applicationData.student.person.lastName;
8575
if (!studentLastName) {
86-
Promise.reject(new Error("No student last name"))
76+
Promise.reject(new Error("No student last name"));
8777
}
8878

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`;
9181
}
9282

9383
// 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;
9686

97-
const application = await db("application").where({ id: this.#applicationId }).first()
87+
const application = await db("application").where({ id: this.#applicationId }).first();
9888
if (!application) {
99-
return Promise.reject(new Error("Application not found"))
89+
return Promise.reject(new Error("Application not found"));
10090
}
10191

102-
const student = await db("student").where({ id: application.studentId }).first()
92+
const student = await db("student").where({ id: application.studentId }).first();
10393
if (!student) {
104-
return Promise.reject(new Error("Student not found"))
94+
return Promise.reject(new Error("Student not found"));
10595
}
10696

107-
const person = await db("person").where({ id: student.personId }).first()
97+
const person = await db("person").where({ id: student.personId }).first();
10898
if (!person) {
109-
return Promise.reject(new Error("Person not found"))
99+
return Promise.reject(new Error("Person not found"));
110100
}
111101

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;
114149

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+
}
116158
// return this.#applicationData
117159
// TODO: replace dummy data with real data
118160
return Promise.resolve({
119161
currentDate: new Date(),
120162
// Example content
121163
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,
130172
},
131173
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,
139183
},
140-
disembursements: [
141-
{ amountInCents: 1000000, releaseDate: new Date("2023-09-15") },
142-
{ amountInCents: 1500000, releaseDate: new Date("2023-12-15") },
143-
],
184+
disbursements,
144185
studentFinancialAssistanceOfficer: {
145-
firstName: "Samantha",
146-
lastName: "Smith",
186+
name,
187+
position
147188
},
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+
}
149208
}
150209
}

0 commit comments

Comments
 (0)