Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/harsh steven/export vsr bulk #35

Merged
merged 36 commits into from
Apr 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e3b2915
initial scaffolding for excel api
2s2e Apr 11, 2024
54a1285
wrote some code to write to a spreadsheet
2s2e Apr 11, 2024
bf782ce
Added frontend api call for bulk excel export
HarshGurnani Apr 12, 2024
2f3a61c
Made it so that the downloaded xlsx file is more properly formatted
2s2e Apr 12, 2024
ac69d66
Reformatted array brackets
2s2e Apr 13, 2024
828502f
added success and error popups
HarshGurnani Apr 13, 2024
8a71755
Merge branch 'feature/harsh-steven/export-vsr-bulk' of github.com:Tri…
HarshGurnani Apr 13, 2024
bc71c1f
added loading state to export button
HarshGurnani Apr 16, 2024
e2dbc01
Fix image path issue with no internet error message
benjaminJohnson2204 Apr 18, 2024
20fcb1e
Clean up VSRs spreadsheet formatting
benjaminJohnson2204 Apr 18, 2024
42ea73f
Minor frontend changes for exporting VSRs
benjaminJohnson2204 Apr 18, 2024
431dd42
Remove Excel file from backend filesystem after sending to client
benjaminJohnson2204 Apr 18, 2024
8e4b729
Add backend tests for exporting VSRs
benjaminJohnson2204 Apr 18, 2024
3f9797c
Capitalize Twin Mat. in furniture items list
benjaminJohnson2204 Apr 18, 2024
831652a
Re-order VSR status options & remove Resubmit
benjaminJohnson2204 Apr 18, 2024
f80a1b4
Fix VSR page & submission messages about emails
benjaminJohnson2204 Apr 18, 2024
e23d1f2
Fix text opacity for dropdown input placeholder
benjaminJohnson2204 Apr 18, 2024
bc84f5e
Dont show asterisks on children input on VSR edit view
benjaminJohnson2204 Apr 18, 2024
6b0e3f1
Rename other field placeholder to Please specify
benjaminJohnson2204 Apr 18, 2024
8970540
Disable status approve button & dropdown when editing VSR
benjaminJohnson2204 Apr 18, 2024
adef760
Rename N/A to No items selected for several VSR fields
benjaminJohnson2204 Apr 18, 2024
ad3b163
Fix validation error with multiple choice & other fields on VSR edit …
benjaminJohnson2204 Apr 18, 2024
f9cdac9
Fix search bar width on VSR table
benjaminJohnson2204 Apr 18, 2024
3537427
Add new backend build for deployment
benjaminJohnson2204 Apr 18, 2024
53783b4
Redo package-lock.json to hopefully fix deployment issue
benjaminJohnson2204 Apr 18, 2024
8f6902f
Try (hopefully) working version of backend/package-lock.json
benjaminJohnson2204 Apr 18, 2024
a410612
Change VSR export function to send file directly to response instead …
benjaminJohnson2204 Apr 18, 2024
b7d8645
Rebuild backend w/ VSR export changes
benjaminJohnson2204 Apr 18, 2024
0324713
Move fields between personal & contact info sections of VSR individual
benjaminJohnson2204 Apr 19, 2024
35dd498
Fix linter warning
benjaminJohnson2204 Apr 19, 2024
f773974
Add spacing between VSR table columns
benjaminJohnson2204 Apr 19, 2024
2f38bc0
Add ability to export only some VSRs from table by selecting
benjaminJohnson2204 Apr 19, 2024
801e01e
Change boolean fields in VSR excel sheet from true/false to yes/no
benjaminJohnson2204 Apr 19, 2024
9d8094e
Add new backend build
benjaminJohnson2204 Apr 19, 2024
57259cc
Test backend change to fix deployment
benjaminJohnson2204 Apr 19, 2024
8e9c77d
Attempt to fix TS error in backend
benjaminJohnson2204 Apr 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion backend/__tests__/furnitureItems.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[
{
"category": "bedroom",
"name": "Twin mat.",
"name": "Twin Mat.",
"allowMultiple": true,
"categoryIndex": 1
},
Expand Down
27 changes: 26 additions & 1 deletion backend/__tests__/vsrTest.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("VSR Tests", () => {
it("GET /api/vsr returns all submitted VSRs to admin", async () => {
await Promise.all(Array(3).fill(null).map(createTestVSR));

const { testToken } = await signInAsRole(UserRole.STAFF);
const { testToken } = await signInAsRole(UserRole.ADMIN);

const res = await request(app).get("/api/vsr").set("Authorization", `Bearer ${testToken}`);
expect(res.statusCode).toBe(200);
Expand Down Expand Up @@ -357,4 +357,29 @@ describe("VSR Tests", () => {
expect(currentVsr!.name).toBe("Updated name");
expect(currentVsr!.email).toBe("[email protected]");
});

it("GET /api/vsr/bulk_export requires user to be signed in", async () => {
const res = await request(app).get("/api/vsr/bulk_export");
expect(res.statusCode).toBe(401);
});

it("GET /api/vsr/bulk_export as staff with no VSRs in database", async () => {
const { testToken } = await signInAsRole(UserRole.STAFF);

const res = await request(app)
.get("/api/vsr/bulk_export")
.set("Authorization", `Bearer ${testToken}`);
expect(res.statusCode).toBe(200);
});

it("GET /api/vsr/bulk_export as staff with VSRs in database", async () => {
await Promise.all(Array(3).fill(null).map(createTestVSR));

const { testToken } = await signInAsRole(UserRole.STAFF);

const res = await request(app)
.get("/api/vsr/bulk_export")
.set("Authorization", `Bearer ${testToken}`);
expect(res.statusCode).toBe(200);
});
});
129 changes: 128 additions & 1 deletion backend/dist/src/controllers/vsr.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,15 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.deleteVSR = exports.updateVSR = exports.updateStatus = exports.createVSR = exports.getVSR = exports.getAllVSRS = void 0;
exports.bulkExportVSRS = exports.deleteVSR = exports.updateVSR = exports.updateStatus = exports.createVSR = exports.getVSR = exports.getAllVSRS = void 0;
const express_validator_1 = require("express-validator");
const http_errors_1 = __importDefault(require("http-errors"));
const furnitureItem_1 = __importDefault(require("../models/furnitureItem"));
const vsr_1 = __importDefault(require("../models/vsr"));
const emails_1 = require("../services/emails");
const validationErrorParser_1 = __importDefault(require("../util/validationErrorParser"));
const exceljs_1 = __importDefault(require("exceljs"));
const mongodb_1 = require("mongodb");
/**
* Gets all VSRs in the database. Requires the user to be signed in and have
* staff or admin permission.
Expand Down Expand Up @@ -142,3 +145,127 @@ const deleteVSR = (req, res, next) => __awaiter(void 0, void 0, void 0, function
}
});
exports.deleteVSR = deleteVSR;
/**
* Converts an entry in a VSR to a formatted string to write to the Excel spreadsheet
*/
const stringifyEntry = (entry) => {
if (entry === undefined || entry === null) {
return "";
}
if (Array.isArray(entry)) {
return entry.join(", ");
}
else if (typeof entry === "boolean") {
return entry ? "yes" : "no";
}
else {
return entry.toString();
}
};
/**
* Formats a VSR's selected furniture items as a string
*/
const stringifySelectedFurnitureItems = (selectedItems, allFurnitureItems) => {
if (!selectedItems) {
return "";
}
const itemIdsToItems = {};
for (const furnitureItem of allFurnitureItems) {
itemIdsToItems[furnitureItem._id.toString()] = furnitureItem;
}
return selectedItems
.map((selectedItem) => {
const furnitureItem = itemIdsToItems[selectedItem.furnitureItemId];
return furnitureItem ? `${furnitureItem.name}: ${selectedItem.quantity}` : "[unknown]";
})
.join(", ");
};
const writeSpreadsheet = (plainVsrs, res) => __awaiter(void 0, void 0, void 0, function* () {
const workbook = new exceljs_1.default.Workbook();
workbook.creator = "PAP Inventory System";
workbook.lastModifiedBy = "Bot";
//current date
workbook.created = new Date();
workbook.modified = new Date();
workbook.lastPrinted = new Date();
const worksheet = workbook.addWorksheet("New Sheet");
// Fields that we want to write to the spreadsheet. First is field name, second is display name.
const fieldsToWrite = [
["name", "Name"],
["gender", "Gender"],
["age", "Age"],
["maritalStatus", "Marital Status"],
["spouseName", "Spouse Name"],
["agesOfBoys", "Ages of boys"],
["agesOfGirls", "Ages of girls"],
["ethnicity", "Ethnicity"],
["employmentStatus", "Employment Status"],
["incomeLevel", "Income Level"],
["sizeOfHome", "Size of Home"],
["streetAddress", "Street Address"],
["city", "City"],
["state", "State"],
["zipCode", "Zip Code"],
["phoneNumber", "Phone Number"],
["email", "Email Address"],
["branch", "Branch"],
["conflicts", "Conflicts"],
["dischargeStatus", "Discharge Status"],
["serviceConnected", "Service Connected"],
["lastRank", "Last Rank"],
["militaryID", "Military ID"],
["petCompanion", "Pet Companion Desired"],
["hearFrom", "Referral Source"],
["selectedFurnitureItems", "Selected Furniture Items"],
["additionalItems", "Additional Items"],
["dateReceived", "Date Received"],
["lastUpdated", "Last Updated"],
["status", "Status"],
];
worksheet.columns = fieldsToWrite.map((field) => ({
header: field[1],
key: field[0],
width: 20,
}));
const allFurnitureItems = yield furnitureItem_1.default.find();
// Add data rows to the worksheet
plainVsrs.forEach((vsr) => {
worksheet.addRow(fieldsToWrite.reduce((prev, field) => (Object.assign(Object.assign({}, prev), { [field[0]]: field[0] === "selectedFurnitureItems"
? stringifySelectedFurnitureItems(vsr[field[0]], allFurnitureItems)
: stringifyEntry(vsr[field[0]]) })), {}));
});
// Write to file
yield workbook.xlsx.write(res);
});
const bulkExportVSRS = (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
var _a, _b;
try {
const filename = "vsrs.xlsx";
// Set some headers on the response so the client knows that a file is attached
res.set({
"Content-Disposition": `attachment; filename="${filename}"`,
"Content-Type": "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
});
let vsrs;
if (req.query.vsrIds && ((_a = req.query.vsrIds.length) !== null && _a !== void 0 ? _a : 0) > 0) {
// If the "vsrIds" query parameter exists and is non-empty, then find & export all VSRs
// with an _id in the vsrIds list
// Need to convert each ID string to an ObjectId object
const vsrObjectIds = (_b = req.query.vsrIds) === null || _b === void 0 ? void 0 : _b.split(",").map((_id) => new mongodb_1.ObjectId(_id));
vsrs = (yield vsr_1.default.find({
_id: {
$in: vsrObjectIds,
},
})).map((doc) => doc.toObject());
}
else {
// If the "vsrIds" query parameter is not provided or is empty, export all VSRs in the database
vsrs = (yield vsr_1.default.find()).map((doc) => doc.toObject());
}
yield writeSpreadsheet(vsrs, res);
}
catch (error) {
next(error);
}
});
exports.bulkExportVSRS = bulkExportVSRS;
5 changes: 3 additions & 2 deletions backend/dist/src/models/vsr.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.furntitureInputSchema = void 0;
const mongoose_1 = require("mongoose");
/**
* A schema for a single furniture item that a veteran can request
*/
const furntitureInputSchema = new mongoose_1.Schema({
exports.furntitureInputSchema = new mongoose_1.Schema({
// ID of the furniture item being required (Object ID for an instance of the furniture item model)
furnitureItemId: { type: String, required: true },
// Quantity being requested by this veteran
Expand Down Expand Up @@ -43,7 +44,7 @@ const vsrSchema = new mongoose_1.Schema({
petCompanion: { type: Boolean, required: true },
hearFrom: { type: String, required: true },
/** Page 3 of VSR */
selectedFurnitureItems: { type: [furntitureInputSchema], required: true },
selectedFurnitureItems: { type: [exports.furntitureInputSchema], required: true },
additionalItems: { type: String, required: false },
/** Fields that are created/updated automatically or on staff side */
dateReceived: { type: Date, required: true },
Expand Down
1 change: 1 addition & 0 deletions backend/dist/src/routes/vsr.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const VSRController = __importStar(require("../controllers/vsr"));
const auth_1 = require("../middleware/auth");
const VSRValidator = __importStar(require("../validators/vsr"));
const router = express_1.default.Router();
router.get("/bulk_export", auth_1.requireSignedIn, auth_1.requireStaffOrAdmin, VSRController.bulkExportVSRS);
router.get("/", auth_1.requireSignedIn, auth_1.requireStaffOrAdmin, VSRController.getAllVSRS);
router.post("/", VSRValidator.createVSR, VSRController.createVSR);
router.get("/:id", auth_1.requireSignedIn, auth_1.requireStaffOrAdmin, VSRController.getVSR);
Expand Down
3 changes: 1 addition & 2 deletions backend/dist/src/validators/vsr.js
Original file line number Diff line number Diff line change
Expand Up @@ -139,10 +139,9 @@ const makeHearFromValidator = () => (0, express_validator_1.body)("hearFrom")
.withMessage("Referral source must be a string");
const ALLOWED_STATUSES = [
"Received",
"Appointment Scheduled",
"Approved",
"Appointment Scheduled",
"Complete",
"Resubmit",
"No-show / Incomplete",
"Archived",
];
Expand Down
Loading
Loading