Skip to content
This repository has been archived by the owner on Dec 18, 2024. It is now read-only.

Commit

Permalink
feat(reports): create endpoint for generating customer item report xl…
Browse files Browse the repository at this point in the history
…sx files
  • Loading branch information
AdrianAndersen committed Jul 2, 2024
1 parent a93101f commit 28df399
Show file tree
Hide file tree
Showing 4 changed files with 248 additions and 1 deletion.
4 changes: 3 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,8 @@
"twilio": "^4.11.2",
"typedjson-npm": "^0.1.7",
"validator": "^13.6.0",
"winston": "^3.3.3"
"winston": "^3.3.3",
"xlsx": "^0.18.5"
},
"devDependencies": {
"@types/chai": "^4.3.16",
Expand All @@ -68,6 +69,7 @@
"@types/sinon": "^17.0.3",
"@types/sinon-chai": "^3.2.12",
"@types/validator": "^13.12.0",
"@types/xlsx": "^0.0.36",
"@typescript-eslint/eslint-plugin": "^7.14.1",
"@typescript-eslint/parser": "^7.14.1",
"chai": "^4.4.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
import { BlapiResponse, BlError, CustomerItem } from "@boklisten/bl-model";
import { Request, Response } from "express";
import moment from "moment-timezone";
import { ObjectId } from "mongodb";
import { utils, write } from "xlsx";

import { customerItemSchema } from "./customer-item.schema";
import { isBoolean, isNullish } from "../../helper/typescript-helpers";
import { Operation } from "../../operation/operation";
import { BlApiRequest } from "../../request/bl-api-request";
import { BlDocumentStorage } from "../../storage/blDocumentStorage";
import { BlCollectionName } from "../bl-collection";

export interface CustomerItemGenerateReportSpec {
branchFilter?: string[];
createdAfter?: string;
createdBefore?: string;
returned: boolean;
handout: boolean;
buyout: boolean;
}

export function verifyCustomerItemGenerateReportSpec(
customerItemGenerateReportSpec: unknown,
): customerItemGenerateReportSpec is CustomerItemGenerateReportSpec {
const m = customerItemGenerateReportSpec as
| Record<string, unknown>
| null
| undefined;
return (
!!m &&
isBoolean(m["returned"]) &&
isBoolean(m["handout"]) &&
isBoolean(m["buyout"]) &&
(isNullish(m["branchFilter"]) ||
(Array.isArray(m["branchFilter"]) &&
m["branchFilter"].every((branchId) => ObjectId.isValid(branchId)))) &&
(isNullish(m["createdAfter"]) ||
(typeof m["createdAfter"] === "string" &&
!isNaN(new Date(m["createdAfter"]).getTime()))) &&
(isNullish(m["createdBefore"]) ||
(typeof m["createdBefore"] === "string" &&
!isNaN(new Date(m["createdBefore"]).getTime())))
);
}

export class CustomerItemGenerateReportOperation implements Operation {
private readonly _customerItemStorage: BlDocumentStorage<CustomerItem>;

constructor(customerItemStorage?: BlDocumentStorage<CustomerItem>) {
this._customerItemStorage =
customerItemStorage ??
new BlDocumentStorage(BlCollectionName.CustomerItems, customerItemSchema);
}

async run(
blApiRequest: BlApiRequest,
_req: Request,
res: Response,
): Promise<BlapiResponse> {
const customerItemGenerateReportSpec = blApiRequest.data;
if (!verifyCustomerItemGenerateReportSpec(customerItemGenerateReportSpec)) {
throw new BlError(`Malformed CustomerItemGenerateReportSpec`).code(701);
}
const filterByHandoutBranchIfPresent =
customerItemGenerateReportSpec.branchFilter
? {
"handoutInfo.handoutById": {
$in: customerItemGenerateReportSpec.branchFilter.map(
(id) => new ObjectId(id),
),
},
}
: {};

const creationTimeLimiter: Record<string, Date> = {};
if (customerItemGenerateReportSpec.createdAfter) {
creationTimeLimiter["$gte"] = new Date(
customerItemGenerateReportSpec.createdAfter,
);
}
if (customerItemGenerateReportSpec.createdBefore) {
creationTimeLimiter["$lte"] = new Date(
customerItemGenerateReportSpec.createdBefore,
);
}
const creationTimeFilter =
Object.keys(creationTimeLimiter).length > 0
? { creationTime: creationTimeLimiter }
: {};

const reportData = await this._customerItemStorage.aggregate([
{
$match: {
returned: customerItemGenerateReportSpec.returned,
buyout: customerItemGenerateReportSpec.buyout,
handout: customerItemGenerateReportSpec.handout,
...filterByHandoutBranchIfPresent,
...creationTimeFilter,
},
},
{
$lookup: {
from: "branches",
localField: "handoutInfo.handoutById",
foreignField: "_id",
as: "branchInfo",
},
},
{
$lookup: {
from: "items",
localField: "item",
foreignField: "_id",
as: "itemInfo",
},
},
{
$addFields: {
customer: {
$toObjectId: "$customer",
},
},
},
{
$lookup: {
from: "userdetails",
localField: "customer",
foreignField: "_id",
as: "customerInfo",
},
},
{
$project: {
handoutBranch: { $first: "$branchInfo.name" },
handoutTime: "$handoutInfo.time",
lastUpdated: 1,
deadline: 1,
blid: 1,
title: { $first: "$itemInfo.title" },
isbn: { $first: "$itemInfo.info.isbn" },
name: { $first: "$customerInfo.name" },
email: { $first: "$customerInfo.email" },
phone: { $first: "$customerInfo.phone" },
dob: { $first: "$customerInfo.dob" },
pivot: "1",
},
},
]);
const workbook = utils.book_new();
const worksheet = utils.json_to_sheet(reportData);

utils.book_append_sheet(workbook, worksheet, "Sheet1");

const buffer = write(workbook, { bookType: "xlsx", type: "buffer" });

res.setHeader(
"Content-Disposition",
`attachment; filename="customer_item_report_${moment().format("YYYY-MM-DD_HH.mm")}.xlsx"`,
);
res.setHeader(
"Content-Type",
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
);

res.send(buffer).end();

return new BlapiResponse([]);
}
}
10 changes: 10 additions & 0 deletions src/collections/customer-item/customer-item.collection.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { CustomerItemGenerateReportOperation } from "./customer-item-generate-report.operation";
import { customerItemSchema } from "./customer-item.schema";
import { CustomerItemPostHook } from "./hooks/customer-item-post.hook";
import { itemSchema } from "../../collections/item/item.schema";
Expand Down Expand Up @@ -33,6 +34,15 @@ export class CustomerItemCollection implements BlCollection {
{
method: "post",
hook: new CustomerItemPostHook(),
operations: [
{
name: "generate-report",
operation: new CustomerItemGenerateReportOperation(),
restriction: {
permissions: ["admin", "super"],
},
},
],
restriction: {
permissions: ["employee", "manager", "admin", "super"],
},
Expand Down
65 changes: 65 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -777,6 +777,13 @@
dependencies:
"@types/webidl-conversions" "*"

"@types/xlsx@^0.0.36":
version "0.0.36"
resolved "https://registry.yarnpkg.com/@types/xlsx/-/xlsx-0.0.36.tgz#b5062003e5c5374ab4f08fdd3bf69da4d4013af8"
integrity sha512-mvfrKiKKMErQzLMF8ElYEH21qxWCZtN59pHhWGmWCWFJStYdMWjkDSAy6mGowFxHXaXZWe5/TW7pBUiWclIVOw==
dependencies:
xlsx "*"

"@types/yauzl@^2.9.1":
version "2.10.0"
resolved "https://registry.yarnpkg.com/@types/yauzl/-/yauzl-2.10.0.tgz#b3248295276cf8c6f153ebe6a9aba0c988cb2599"
Expand Down Expand Up @@ -908,6 +915,11 @@ acorn@^8.9.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.9.0.tgz#78a16e3b2bcc198c10822786fa6679e245db5b59"
integrity sha512-jaVNAFBHNLXspO543WnNNPZFRtavh3skAkITqD0/2aeMkKZTN+254PyhwxFYrk3vQ1xfY+2wbesJMs/JC8/PwQ==

adler-32@~1.3.0:
version "1.3.1"
resolved "https://registry.yarnpkg.com/adler-32/-/adler-32-1.3.1.tgz#1dbf0b36dda0012189a32b3679061932df1821e2"
integrity sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==

agent-base@6, agent-base@^6.0.0, agent-base@^6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
Expand Down Expand Up @@ -1364,6 +1376,14 @@ caseless@~0.12.0:
resolved "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=

cfb@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/cfb/-/cfb-1.2.2.tgz#94e687628c700e5155436dac05f74e08df23bc44"
integrity sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==
dependencies:
adler-32 "~1.3.0"
crc-32 "~1.2.0"

chai-as-promised@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/chai-as-promised/-/chai-as-promised-7.1.2.tgz#70cd73b74afd519754161386421fb71832c6d041"
Expand Down Expand Up @@ -1496,6 +1516,11 @@ clone@^2.1.2:
resolved "https://registry.yarnpkg.com/clone/-/clone-2.1.2.tgz#1b7f4b9f591f1e8f83670401600345a02887435f"
integrity sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==

codepage@~1.15.0:
version "1.15.0"
resolved "https://registry.yarnpkg.com/codepage/-/codepage-1.15.0.tgz#2e00519024b39424ec66eeb3ec07227e692618ab"
integrity sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==

color-convert@^1.9.0, color-convert@^1.9.3:
version "1.9.3"
resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
Expand Down Expand Up @@ -1639,6 +1664,11 @@ cors@^2.8.5:
object-assign "^4"
vary "^1"

crc-32@~1.2.0, crc-32@~1.2.1:
version "1.2.2"
resolved "https://registry.yarnpkg.com/crc-32/-/crc-32-1.2.2.tgz#3cad35a934b8bf71f25ca524b6da51fb7eace2ff"
integrity sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==

create-require@^1.1.0:
version "1.1.1"
resolved "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz"
Expand Down Expand Up @@ -2735,6 +2765,11 @@ foundation-emails@^2.2.1:
resolved "https://registry.yarnpkg.com/foundation-emails/-/foundation-emails-2.4.0.tgz#5020b44c6a1ee366ad53dd9ba29368d351aef8dd"
integrity sha512-aLma02P+OafCQtcEkZzVAhX1E3LiB4RYCxUu73BRma+YMrq2QOBUr6K8Cw37taL0jehQn4T8yfuWBQ8xv3Mvug==

frac@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/frac/-/frac-1.1.2.tgz#3d74f7f6478c88a1b5020306d747dc6313c74d0b"
integrity sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==

fresh@0.5.2:
version "0.5.2"
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
Expand Down Expand Up @@ -5251,6 +5286,13 @@ specificity@^0.4.1:
resolved "https://registry.yarnpkg.com/specificity/-/specificity-0.4.1.tgz#aab5e645012db08ba182e151165738d00887b019"
integrity sha512-1klA3Gi5PD1Wv9Q0wUoOQN1IWAuPu0D1U03ThXTr0cJ20+/iq2tHSDnK7Kk/0LXJ1ztUB2/1Os0wKmfyNgUQfg==

ssf@~0.11.2:
version "0.11.2"
resolved "https://registry.yarnpkg.com/ssf/-/ssf-0.11.2.tgz#0b99698b237548d088fc43cdf2b70c1a7512c06c"
integrity sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==
dependencies:
frac "~1.1.2"

sshpk@^1.7.0:
version "1.16.1"
resolved "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz"
Expand Down Expand Up @@ -6114,11 +6156,21 @@ winston@^3.3.3:
triple-beam "^1.3.0"
winston-transport "^4.4.0"

wmf@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/wmf/-/wmf-1.0.2.tgz#7d19d621071a08c2bdc6b7e688a9c435298cc2da"
integrity sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==

word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==

word@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/word/-/word-0.3.0.tgz#8542157e4f8e849f4a363a288992d47612db9961"
integrity sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==

wordwrap@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
Expand Down Expand Up @@ -6157,6 +6209,19 @@ ws@^7.2.3:
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==

xlsx@*, xlsx@^0.18.5:
version "0.18.5"
resolved "https://registry.yarnpkg.com/xlsx/-/xlsx-0.18.5.tgz#16711b9113c848076b8a177022799ad356eba7d0"
integrity sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==
dependencies:
adler-32 "~1.3.0"
cfb "~1.2.1"
codepage "~1.15.0"
crc-32 "~1.2.1"
ssf "~0.11.2"
wmf "~1.0.1"
word "~0.3.0"

xmlbuilder@^13.0.2:
version "13.0.2"
resolved "https://registry.yarnpkg.com/xmlbuilder/-/xmlbuilder-13.0.2.tgz#02ae33614b6a047d1c32b5389c1fdacb2bce47a7"
Expand Down

0 comments on commit 28df399

Please sign in to comment.