Skip to content

Commit

Permalink
Merge pull request #282 from icefoganalytics/test
Browse files Browse the repository at this point in the history
Vendoring
  • Loading branch information
datajohnson authored Jul 11, 2024
2 parents f0e8eee + 96d0b65 commit 9c00416
Show file tree
Hide file tree
Showing 10 changed files with 335 additions and 77 deletions.
15 changes: 15 additions & 0 deletions src/api/controllers/admin/reporting-controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,21 @@ export default class ReportingController extends BaseController {
}
});
}

async runVendorUpdateReport() {
return ReportingService.runVendorUpdateReport({ format: this.format ?? "json" }).then(async (reportData) => {
if (this.format == "json") {
this.response.json(reportData);
} else {
let csv = unparse(reportData, {
quotes: true,
});
this.response.setHeader("Content-disposition", `attachment; filename="Vendor request multi-vendor.csv"`);
this.response.setHeader("Content-type", "text/csv");
this.response.send(csv);
}
});
}
}

function formatMoney(input: number) {
Expand Down
1 change: 1 addition & 0 deletions src/api/routes/admin/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,7 @@ adminRouter.use("/reporting/nars2022disrcl/:academic_year_id", routedTo(Reportin
adminRouter.use("/reporting/step/:academic_year_id", routedTo(ReportingController, "runStepReport"));
adminRouter.use("/reporting/approvedFunding/:academic_year_id", routedTo(ReportingController, "runApprovedFundingReport"));
adminRouter.use("/reporting/t4a/:tax_year", routedTo(ReportingController, "runT4AReport"));
adminRouter.use("/reporting/vendor-update", routedTo(ReportingController, "runVendorUpdateReport"));

adminRouter.use("/csg-threshold", csgThresholdRouter);
adminRouter.use("/yea-import", yeaImportRouter);
Expand Down
110 changes: 70 additions & 40 deletions src/api/routes/admin/student-router.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import express, { Request, Response } from "express";
import { body, param } from "express-validator";
import moment from "moment";
import { readFileSync } from "fs";
import knex from "knex";
import { orderBy } from "lodash";
import axios from "axios";
import { ReturnValidationErrors, ReturnValidationErrorsCustomMessage } from "../../middleware";
import { DB_CONFIG } from "../../config";
import { API_PORT, DB_CONFIG } from "../../config";
import { DocumentService } from "@/services/shared";
import { generatePDF } from "@/utils/pdf-generator";
import { create } from "express-handlebars";
let { RequireActive } = require("../auth");

const db = knex(DB_CONFIG);
Expand Down Expand Up @@ -362,7 +365,7 @@ studentRouter.get("/:id", [param("id").notEmpty()], ReturnValidationErrors, asyn

const consentInfo = await db("sfa.student_consent").where({ student_id: id });

const vendorUpdates = await db("sfa.vendor_update").where({ student_id: id });
const vendorUpdates = await db("sfa.vendor_update").where({ student_id: id }).orderBy("created_date", "desc");

const residenceInfo = await db("sfa.residence").where({ student_id: id });

Expand Down Expand Up @@ -1071,14 +1074,12 @@ studentRouter.post(
.innerJoin("sfa.person", "student.person_id", "person_id")
.where({ "student.id": student_id })
.first();
const vendorAddress = await db("sfa.v_current_person_address").where({ id: data.address_id }).first();
const vendorAddress = await db("sfa.person_address").where({ id: data.address_id }).first();

if (student && vendorAddress) {
console.log("STUDENT VENDOR", student.vendor_id);
console.log("BODY", req.body);

const toInsert = {
address: `${vendorAddress.address1}, ${vendorAddress.address2}`,
student_id,
address: `${vendorAddress.address1 ?? ""}, ${vendorAddress.address2 ?? ""}`,
city_id: vendorAddress.city_id,
province_id: vendorAddress.province_id,
postal_code: vendorAddress.postal_code,
Expand All @@ -1088,59 +1089,88 @@ studentRouter.post(
address_type_id: vendorAddress.address_type_id,
vendor_id: student.vendor_id,
created_date: new Date(),
update_requested_date: new Date(),
student_id,
is_address_update: data.is_address_update,
is_banking_update: data.is_banking_update,
is_direct_deposit_update: data.is_direct_deposit_update,
is_name_change_update: data.is_name_change_update,
name_change_comment: data.name_change_comment,
};

await db("sfa.vendor_update").insert(toInsert);

/* return resInsert
? res.json({ messages: [{ variant: "success", text: "Saved" }] })
: res.json({ messages: [{ variant: "error", text: "Failed" }] }); */

return res.json({ messages: [{ variant: "success", text: "Saved" }] });
}

return res.status(404).send("Studend and address not found");
} catch (error) {
console.error(error);
return res.status(400).send({ messages: [{ variant: "error", text: "Failed", error }] });
return res.status(400).send({ messages: [{ variant: "error", text: "Failed" }] });
}
}
);

studentRouter.patch(
studentRouter.get(
"/:student_id/vendor-update/:id",
[param("student_id").isInt().notEmpty(), param("id").isInt().notEmpty()],
[param("student_id").isInt().notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
try {
const { student_id, id } = req.params;
const { data } = req.body;
const { student_id, id } = req.params;
const { format } = req.query;
const update = await db("sfa.vendor_update")
.where({ "vendor_update.id": id, student_id })
.leftOuterJoin("sfa.city", "city.id", "vendor_update.city_id")
.leftOuterJoin("sfa.province", "province.id", "vendor_update.city_id")

.select("vendor_update.*", "city.description as city", "province.description as province")
.first();
const student = await db("sfa.student")
.innerJoin("sfa.person", "person.id", "student.person_id")
.where({ "student.id": student_id })
.first();

const student: any = await db("sfa.student").where({ id: student_id }).first();
if (!update) return res.status(404).send("Vendor Update not found");
if (!student) return res.status(404).send("Student not found");

if (student) {
if (Object.keys(data).some((value) => value === "address_type_id")) {
if (!data.address_type_id) {
return res.json({ messages: [{ variant: "error", text: "Address Type is required" }] });
}
}
const resUpdate = await db("sfa.vendor_update")
.where("id", id)
.where("student_id", student_id)
.update({ ...data, student_id });
student.vendor_id = student.vendor_id ?? "____________________";

return resUpdate > 0
? res.json({ messages: [{ variant: "success", text: "Saved" }] })
: res.json({ messages: [{ variant: "error", text: "Failed" }] });
}
const pdfData = {
API_PORT: API_PORT,
update,
student,
user: req.user,
department: "Department: E-13A",
date: moment().format("D MMM YYYY"),
};
const h = create({ defaultLayout: "./templates/layouts/pdf-layout" });
const data = await h.renderView(__dirname + "/../../templates/admin/vendor/vendor-request.handlebars", {
...pdfData,
});

return res.status(404).send({ messages: [{ variant: "error", text: "Failed" }] });
} catch (error) {
console.error(error);
return res.status(400).send({ messages: [{ variant: "error", text: "Failed", error }] });
}
if (format == "html") return res.send(data);

let name = `VendorRequest-${student.first_name}${student.last_name}`.replace(/\s/g, "");

const footerTemplate = `<div style="width: 100%; text-align: left; font-size: 11px; padding: 5px 0; border-top: 1px solid #ccc; margin: 0 40px 10px; font-family: Calibri;">
<div style="float:left">${moment().format("MMMM D, YYYY")}</div><div style="float:right">Page 1 of 1</div>
</div>`;

let pdf = await generatePDF(data, "letter", false, footerTemplate);
res.setHeader("Content-disposition", `attachment; filename="${name}.pdf"`);
res.setHeader("Content-type", "application/pdf");
res.send(pdf);
}
);

studentRouter.put(
"/:student_id/vendor-update/:id",
[param("student_id").isInt().notEmpty(), param("id").isInt().notEmpty()],
ReturnValidationErrors,
async (req: Request, res: Response) => {
const { student_id, id } = req.params;
const { update_completed_date } = req.body;

await db("sfa.vendor_update").where({ id, student_id }).update({ update_completed_date });

res.json({ messages: [{ variant: "success", text: "Saved" }] });
}
);

Expand Down
47 changes: 45 additions & 2 deletions src/api/services/admin/reporting-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,8 +346,7 @@ export default class ReportingService {

let csl_family_size = 99;


row.csl_family_size = 99
row.csl_family_size = 99;
}

return results;
Expand Down Expand Up @@ -380,6 +379,50 @@ export default class ReportingService {
return results;
}

static async runVendorUpdateReport({ format }: { format: string }): Promise<any[]> {
let results = await db.raw(
`SELECT vendor_update.id, person.first_name, person.last_name, vendor_update.vendor_id, vendor_update.address, city.description as city, province.description as province,
postal_code, country.description as country, vendor_update.telephone, vendor_update.email
FROM [sfa].[vendor_update]
LEFT JOIN sfa.[city] on city.id = [vendor_update].city_id
LEFT JOIN sfa.[province] on province.id = [vendor_update].province_id
LEFT JOIN sfa.[country] on country.id = [vendor_update].country_id
LEFT JOIN sfa.[student] on student.id = [vendor_update].student_id
LEFT JOIN sfa.[person] on person.id = [student].person_id
WHERE update_requested_date IS NULL`
);

const val = results.map((r: any) => {
return {
"Business Name": `${r.firstName} ${r.lastName}`,
"Business Name 2": "",
"Corporate Registry Number": "",
"Existing Vendor id": r.vendorId ?? "",
"Entity Type": "Individual",
"Business type": "",
Street: r.address,
City: r.city,
Province: r.province,
"Postal Code": r.postalCode,
Country: r.country,
"Alt Address": "",
"Phone number": r.telephone,
"Cellphone number": "",
Email: r.email,
};
});

if (format == "csv") {
//since it's a CSV, we need to update the items so they don't show up multiple times

for (let item of results) {
await db("vendor_update").where({ id: item.id }).update({ update_requested_date: new Date() });
}
}

return val;
}

static async generateAs({
format,
reportData,
Expand Down
100 changes: 100 additions & 0 deletions src/api/templates/admin/vendor/vendor-request.handlebars
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
<style>
table td{ font-size: 1.1rem}
</style>
<div style="font-size: 1.1rem">
<div class="header" style="text-align: right; font-size: .9rem; margin-top: -60px">
GOVERNMENT OF YUKON, DEPARTMENT OF EDUCATION
<br />
STUDENT FUNDING PROGRAMS
<div class="mt-4" style="font-size: 1.4rem; font-weight: 700">
Accounts Payable Vendor Update Request
</div>
<hr style="border-color: black; border-bottom: 0; margin-bottom: 25px; margin-top: 15px;" />
</div>

<div style="margin-bottom: 15px">
VENDOR ID - ASSIGNED BY FINANCE:
<strong>{{student.vendor_id}}</strong>
</div>
<div>
Request to:
<ul style="margin-top: 5px">
{{#if update.is_address_update}}
<li>Update VID address to the following</li>
{{/if}}

{{#if update.is_banking_update}}
<li>Update VID's banking information to the new one attached</li>
{{/if}}

{{#if update.is_direct_deposit_update}}
<li>Set up VID for direct deposit with the banking information attached</li>
{{/if}}

{{#if update.is_name_change_update}}
<li>Change the name of the Vendor due to specific circumstances:
<strong>{{update.name_change_comment}}</strong>
</li>
{{/if}}
</ul>
</div>

<table>
<tr>
<td style="width: 140px">Vendor Name:</td>
<td>{{student.first_name}} {{student.initials}} {{student.last_name}}</td>
</tr>
<tr>
<td>Address:</td>
<td>{{update.address}}</td>
</tr>
<tr>
<td>City:</td>
<td>{{update.city}}</td>
</tr>
<tr>
<td>Province/Territory:</td>
<td>{{update.province}}</td>
</tr>
<tr>
<td>Postal Code:</td>
<td>{{update.postal_code}}</td>
</tr>
<tr>
<td>Telephone:</td>
<td>{{student.telephone}}</td>
</tr>
<tr>
<td>Email:</td>
<td>{{student.email}}</td>
</tr>
<tr>
<td>Employee:</td>
<td>No</td>
</tr>
</table>

<table style="width: 100%; margin-top: 80px">
<tr>
<td style="width: 140px">Prepared By:</td>
<td style="border-bottom: 1px black solid;">{{user.first_name}}
{{user.last_name}}
-
{{department}}</td>
<td style="width: 30px"></td>
<td style="width: 45px">Date:</td>
<td style="border-bottom: 1px black solid; width: 100px">{{date}}</td>
<td style=""></td>
</tr>
<tr><td colspan="6">&nbsp;</td></tr>

<tr class="">
<td style="margin-top: 20px">Financial Approval:</td>
<td style="border-bottom: 1px black solid;"></td>
<td style=""></td>
<td style="">Date:</td>
<td style="border-bottom: 1px black solid; "></td>
<td></td>
</tr>
</table>
</div>
2 changes: 1 addition & 1 deletion src/api/templates/layouts/pdf-layout.handlebars
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
color: #000;
}
.header {}
.header { }
.date {
margin-top: 0.139in;
Expand Down
13 changes: 10 additions & 3 deletions src/api/utils/pdf-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,22 @@ import puppeteer, { PaperFormat } from "puppeteer";
export async function generatePDF(
content: string,
format: PaperFormat = "letter",
landscape: boolean = true
landscape: boolean = true,
footerTemplate: string = ""
): Promise<Buffer> {
const browser = await puppeteer.launch({
headless: "new",
args: ["--no-sandbox", "--headless", "--disable-gpu"]
args: ["--no-sandbox", "--headless", "--disable-gpu"],
});
const page = await browser.newPage();
await page.setContent(content);
const pdf = await page.pdf({ format, landscape, preferCSSPageSize: true });
const pdf = await page.pdf({
format,
landscape,
preferCSSPageSize: true,
footerTemplate,
displayHeaderFooter: true,
});
await browser.close();
return Promise.resolve(pdf);
}
Loading

0 comments on commit 9c00416

Please sign in to comment.