Fix: APIs & Schema (#25)

Alof of API Fixes
This commit is contained in:
Sathishkumar Krishnan
2022-03-03 02:09:00 +05:30
committed by GitHub
parent 52d020717c
commit 4763da2d69
33 changed files with 1360 additions and 346 deletions

View File

@@ -2,4 +2,8 @@ API_PORT=9000
MONGODB_URI=
JWT_SECRET=
JWT_REFRESH_EXPIRY_TIME=
JWT_ACCESS_EXPIRY_TIME=
JWT_ACCESS_EXPIRY_TIME=
AWS_S3_BUCKET=
AWS_S3_ACCESS_KEY_ID=
AWS_S3_SECRET_ACCESS_KEY=
AWS_S3_REGION=

3
.gitignore vendored
View File

@@ -104,4 +104,5 @@ dist
.tern-port
uploads/*
uploads/*
tmp/uploads/*

203
package-lock.json generated
View File

@@ -9,6 +9,7 @@
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"aws-sdk": "^2.1082.0",
"bcrypt": "^5.0.1",
"cors": "^2.8.5",
"dotenv": "^10.0.0",
@@ -277,6 +278,45 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"node_modules/aws-sdk": {
"version": "2.1082.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1082.0.tgz",
"integrity": "sha512-aDrUZ63O/ocuC827ursDqFQAm3jhqsJu1DvMCCFg73y+FK9pXXNHp2mwdi3UeeHvtfxISCLCjuyO3VFd/tpVfA==",
"dependencies": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "3.3.2",
"xml2js": "0.4.19"
},
"engines": {
"node": ">= 10.0.0"
}
},
"node_modules/aws-sdk/node_modules/buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"dependencies": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"node_modules/aws-sdk/node_modules/ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"node_modules/aws-sdk/node_modules/isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -1218,6 +1258,14 @@
"node": ">= 0.6"
}
},
"node_modules/events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ=",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/express": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
@@ -1839,6 +1887,14 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"node_modules/jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw==",
"engines": {
"node": ">= 0.6.0"
}
},
"node_modules/js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -2684,6 +2740,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=",
"deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.",
"engines": {
"node": ">=0.4.x"
}
},
"node_modules/range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -2870,6 +2935,11 @@
"node": ">=6"
}
},
"node_modules/sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"node_modules/semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -3289,6 +3359,15 @@
"punycode": "^2.1.0"
}
},
"node_modules/url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
"dependencies": {
"punycode": "1.3.2",
"querystring": "0.2.0"
}
},
"node_modules/url-parse-lax": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
@@ -3301,6 +3380,11 @@
"node": ">=4"
}
},
"node_modules/url/node_modules/punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -3314,6 +3398,15 @@
"node": ">= 0.4.0"
}
},
"node_modules/uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==",
"deprecated": "Please upgrade to version 7 or higher. Older versions may use Math.random() in certain circumstances, which is known to be problematic. See https://v8.dev/blog/math-random for details.",
"bin": {
"uuid": "bin/uuid"
}
},
"node_modules/v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -3443,6 +3536,23 @@
"node": ">=8"
}
},
"node_modules/xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"dependencies": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"node_modules/xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0=",
"engines": {
"node": ">=4.0"
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
@@ -3658,6 +3768,44 @@
"resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz",
"integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI="
},
"aws-sdk": {
"version": "2.1082.0",
"resolved": "https://registry.npmjs.org/aws-sdk/-/aws-sdk-2.1082.0.tgz",
"integrity": "sha512-aDrUZ63O/ocuC827ursDqFQAm3jhqsJu1DvMCCFg73y+FK9pXXNHp2mwdi3UeeHvtfxISCLCjuyO3VFd/tpVfA==",
"requires": {
"buffer": "4.9.2",
"events": "1.1.1",
"ieee754": "1.1.13",
"jmespath": "0.16.0",
"querystring": "0.2.0",
"sax": "1.2.1",
"url": "0.10.3",
"uuid": "3.3.2",
"xml2js": "0.4.19"
},
"dependencies": {
"buffer": {
"version": "4.9.2",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-4.9.2.tgz",
"integrity": "sha512-xq+q3SRMOxGivLhBNaUdC64hDTQwejJ+H0T/NB1XMtTVEwNTrfFF3gAxiyW0Bu/xWEGhjVKgUcMhCrUy2+uCWg==",
"requires": {
"base64-js": "^1.0.2",
"ieee754": "^1.1.4",
"isarray": "^1.0.0"
}
},
"ieee754": {
"version": "1.1.13",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
"integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
},
"isarray": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
"integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE="
}
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -4379,6 +4527,11 @@
"resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz",
"integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc="
},
"events": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/events/-/events-1.1.1.tgz",
"integrity": "sha1-nr23Y1rQmccNzEwqH1AEKI6L2SQ="
},
"express": {
"version": "4.17.2",
"resolved": "https://registry.npmjs.org/express/-/express-4.17.2.tgz",
@@ -4854,6 +5007,11 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"jmespath": {
"version": "0.16.0",
"resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz",
"integrity": "sha512-9FzQjJ7MATs1tSpnco1K6ayiYE3figslrXA72G2HQ/n76RzvYlofyi5QM+iX4YRs/pu3yzxlVQSST23+dMDknw=="
},
"js-yaml": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
@@ -5515,6 +5673,11 @@
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.6.tgz",
"integrity": "sha512-TIRk4aqYLNoJUbd+g2lEdz5kLWIuTMRagAXxl78Q0RiVjAOugHmeKNGdd3cwo/ktpf9aL9epCfFqWDEKysUlLQ=="
},
"querystring": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz",
"integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA="
},
"range-parser": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz",
@@ -5647,6 +5810,11 @@
"sparse-bitfield": "^3.0.3"
}
},
"sax": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/sax/-/sax-1.2.1.tgz",
"integrity": "sha1-e45lYZCyKOgaZq6nSEgNgozS03o="
},
"semver": {
"version": "7.3.5",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
@@ -5981,6 +6149,22 @@
"punycode": "^2.1.0"
}
},
"url": {
"version": "0.10.3",
"resolved": "https://registry.npmjs.org/url/-/url-0.10.3.tgz",
"integrity": "sha1-Ah5NnHcF8hu/N9A861h2dAJ3TGQ=",
"requires": {
"punycode": "1.3.2",
"querystring": "0.2.0"
},
"dependencies": {
"punycode": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz",
"integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0="
}
}
},
"url-parse-lax": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-3.0.0.tgz",
@@ -6000,6 +6184,11 @@
"resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz",
"integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM="
},
"uuid": {
"version": "3.3.2",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz",
"integrity": "sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA=="
},
"v8-compile-cache": {
"version": "2.3.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
@@ -6096,6 +6285,20 @@
"integrity": "sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q==",
"dev": true
},
"xml2js": {
"version": "0.4.19",
"resolved": "https://registry.npmjs.org/xml2js/-/xml2js-0.4.19.tgz",
"integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==",
"requires": {
"sax": ">=0.6.0",
"xmlbuilder": "~9.0.1"
}
},
"xmlbuilder": {
"version": "9.0.7",
"resolved": "https://registry.npmjs.org/xmlbuilder/-/xmlbuilder-9.0.7.tgz",
"integrity": "sha1-Ey7mPS7FVlxVfiD0wi35rKaGsQ0="
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",

View File

@@ -20,6 +20,7 @@
},
"homepage": "https://github.com/kfnawaz/plaidware-wms-core#readme",
"dependencies": {
"aws-sdk": "^2.1082.0",
"bcrypt": "^5.0.1",
"cors": "^2.8.5",
"dotenv": "^10.0.0",

44
src/config/aws.js Normal file
View File

@@ -0,0 +1,44 @@
const AWS = require("aws-sdk");
const fs = require("fs");
const { AWS_S3_ACCESS_KEY_ID, AWS_S3_SECRET_ACCESS_KEY, AWS_S3_BUCKET, AWS_S3_REGION } = require("./env");
AWS.config.update({
maxRetries: 3,
accessKeyId: AWS_S3_ACCESS_KEY_ID,
secretAccessKey: AWS_S3_SECRET_ACCESS_KEY,
region: AWS_S3_REGION,
});
const S3 = new AWS.S3();
module.exports = {
S3: {
uploadFile: async (key, filepath) => {
const fileReadStream = fs.createReadStream(filepath);
const params = {
Bucket: AWS_S3_BUCKET,
Key: key,
Body: fileReadStream,
};
try {
const response = await S3.upload(params).promise();
console.log("S3 Upload success", response);
fs.rmSync(filepath);
return response.Location;
} catch (error) {
console.log("S3 Upload Error", error);
fs.rmSync(filepath);
return false;
}
},
generatePresignedUrl: (url) => {
const key = url.split(".com/")[1];
return S3.getSignedUrl("getObject", {
Bucket: AWS_S3_BUCKET,
Key: key,
Expires: 600,
});
}
},
};

View File

@@ -4,9 +4,12 @@ const envVariables = {
API_PORT: process.env.API_PORT || "3000",
MONGODB_URI: process.env.MONGODB_URI || "mongodb://localhost:12017",
JWT_SECRET: process.env.JWT_SECRET || "secret123",
JWT_REFRESH_EXPIRY_TIME:
parseInt(process.env.JWT_REFRESH_EXPIRY_TIME) || 3600,
JWT_REFRESH_EXPIRY_TIME: parseInt(process.env.JWT_REFRESH_EXPIRY_TIME) || 3600,
JWT_ACCESS_EXPIRY_TIME: parseInt(process.env.JWT_ACCESS_EXPIRY_TIME) || 86400,
AWS_S3_BUCKET: process.env.AWS_S3_BUCKET,
AWS_S3_ACCESS_KEY_ID: process.env.AWS_S3_ACCESS_KEY_ID,
AWS_S3_SECRET_ACCESS_KEY: process.env.AWS_S3_SECRET_ACCESS_KEY,
AWS_S3_REGION: process.env.AWS_S3_REGION || "us-east-2",
};
module.exports = envVariables;

View File

@@ -10,6 +10,7 @@ const Sublevel = require("../models/Sublevel");
const Inventory = require("../models/Inventory");
const WidgetFamily = require("../models/WidgetFamily");
const Item = require("../models/Item");
const { deleteSubLevelTreeFromRoot } = require("./utils/sublevel");
const createWarehouse = async ({ name, address, specs, company_id }) => {
if (!(name && address)) {
@@ -251,20 +252,20 @@ const createWidgetFamilies = async (widgetFamilies, inventory, parent = undefine
return widgetFamiliesData;
};
const getChildModel = (parentType) => {
switch (parentType) {
const getLocationTypeModel = (type) => {
switch (type) {
case "warehouse":
return Zone;
return Warehouse;
case "zone":
return Area;
return Zone;
case "area":
return Row;
return Area;
case "row":
return Bay;
return Row;
case "bay":
return Level;
return Bay;
case "level":
return Sublevel;
return Level;
case "sublevel":
return Sublevel;
default:
@@ -272,6 +273,26 @@ const getChildModel = (parentType) => {
}
};
const getChildType = (parentType) => {
switch (parentType) {
case "warehouse":
return "zone";
case "zone":
return "area";
case "area":
return "row";
case "row":
return "bay";
case "bay":
return "level";
case "level":
case "sublevel":
return "sublevel";
default:
throw new Error("Invalid model type");
}
};
module.exports = {
createWarehouseSchema: async (req, res, next) => {
try {
@@ -356,13 +377,53 @@ module.exports = {
const { id, type } = req.body;
if (!id || !type) return res.send({ success: false, message: "Missing id or type" });
const query = {};
query[`${type}_id`] = id;
let query = {};
switch (type) {
case "level":
case "sublevel":
query = { $or: [{ main_level_id: id, parent_sublevel_id: id }] };
break;
default:
query[`${type}_id`] = id;
break;
}
let childrenData = await getLocationTypeModel(getChildType(type)).find(query);
// populate locations to sublevel
if (childrenData && ["level", "sublevel"].includes(type)) {
const parentData = type === "level" ? await Level.findById(id) : await Sublevel.findById(id);
childrenData =
parentData &&
childrenData.map((child) => ({
...child?._doc,
positions: parentData.sub_levels?.find((t2) => t2.sub_level_id.toString() === child._id.toString())?.positions,
}));
}
const childrenData = await getChildModel(type).find(query);
res.send({ success: true, data: { parent: { id, type }, childrenData } });
} catch (error) {
next(error);
}
},
deleteByIdAndLocationType: async (req, res, next) => {
const { id, type } = req.body;
let removedIDs;
try {
switch (type) {
case "sublevel":
removedIDs = await deleteSubLevelTreeFromRoot(id);
break;
default:
removedIDs = await getLocationTypeModel(type).deleteOne({ _id: id });
break;
}
res.send({ success: true, data: { removedIDs, deleted: { id, type } } });
} catch (error) {
next(error);
}
},
};

View File

@@ -16,4 +16,9 @@ router.post("/create-inventory-schema", controller.createInventorySchema);
*/
router.post("/get-children-from-parent", controller.getChildrenFromParent);
/**
* @route /dashboard/location
*/
router.post("/delete-location", controller.deleteByIdAndLocationType);
module.exports = router;

View File

@@ -1,3 +1,5 @@
const mongoose = require("mongoose");
const Inventory = require("../models/Inventory");
const WidgetFamily = require("../models/WidgetFamily");
const { InventoryTypes } = require("../config/constants");
@@ -17,16 +19,40 @@ module.exports = {
const { id } = req.params;
if (!id) {
res.status(400).send("Missing id param");
res.status(400).send({ success: false, error: "Missing id param" });
return;
}
try {
const inventoryData = await Inventory.findById(id);
if (!inventoryData) {
res.status(404).send({ success: false, error: "Inventory not found" });
return;
}
res.send({ success: true, data: inventoryData });
} catch (error) {
next(error);
}
},
getInventories: async (req, res, next) => {
let { page, perPage } = req.query;
page = page ? parseInt(page) : 0;
perPage = perPage ? parseInt(perPage) : 10;
try {
const inventoryData = await Inventory.find()
.skip(parseInt(page) * parseInt(perPage))
.limit(parseInt(perPage));
if (!inventoryData) {
res.status(404);
return;
}
for (const inventory of inventoryData) {
inventory["widgetFamilies"] = await WidgetFamily.find({ inventory: inventory._id });
}
res.send({ success: true, data: inventoryData });
} catch (error) {
next(error);
@@ -37,10 +63,10 @@ module.exports = {
* Create a Inventory
*/
createInventory: async (req, res, next) => {
const { name, type, policies } = req.body;
let { name, policies, widgetName, icon_slug } = req.body;
if (!(name && type)) {
res.status(400).send("Missing params param");
if (!(name && widgetName && icon_slug)) {
res.status(400).send("Missing params");
return;
}
const preferredLocations = [];
@@ -48,33 +74,32 @@ module.exports = {
for (const preferredLocation of policies.preferredLocations) {
preferredLocations.push({ id: preferredLocation.id, type: preferredLocation.type });
}
} else if (!policies) {
policies = {};
}
const verifiedPolicies = {
orderTracking: policies.orderTracking || {},
alerting: {
lowestStockLevel: policies.alerting && policies.alerting.lowestStockLevel ? policies.alerting.lowestStockLevel : false,
highestStockLevel: policies.alerting && policies.alerting.highestStockLevel ? policies.alerting.highestStockLevel : false,
alertStockLevel: policies.alerting && policies.alerting.alertStockLevel ? policies.alerting.alertStockLevel : false,
reOrderLevel: policies.alerting && policies.alerting.reOrderLevel ? policies.alerting.reOrderLevel : false,
},
replenishment: policies.replenishment || {},
preferredLocations: preferredLocations,
orderTracking: policies.orderTracking || false,
alerting: policies.alerting || false,
replenishment: policies.replenishment || false,
preferredLocations: preferredLocations || false,
inventory_process: policies.inventory_process,
};
try {
const inventoryData = new Inventory({
name,
type,
widgetName,
policies: verifiedPolicies,
icon_slug,
});
await inventoryData.save();
if (!inventoryData) {
res.status(404);
return;
}
res.send({ success: true, data: inventoryData });
await inventoryData.save();
res.send({ success: true, data: { inventoryData } });
} catch (error) {
next(error);
}
@@ -84,65 +109,49 @@ module.exports = {
* Update a Inventory detail
*/
updateInventoryByID: async (req, res, next) => {
const { id } = req.params;
if (!id) {
res.status(400).send("Missing id param");
return;
}
const { name, type, policies } = req.body;
if (!(name || type)) {
res.status(400).send("Missing data in body");
return;
}
try {
const inventoryData = await Inventory.findById(id);
if (!inventoryData) {
res.status(404);
const { id } = req.params;
if (!(id && mongoose.isValidObjectId(id))) {
res.status(400).send("Missing/Improper id param");
return;
}
if (name) inventoryData.name = name;
if (type) inventoryData.type = type;
const inventory = await Inventory.findById(id);
if (policies) {
const preferredLocations = [];
if (policies.preferredLocations && Array.isArray(policies.preferredLocations)) {
for (const preferredLocation of policies.preferredLocations) {
preferredLocations.push({ id: preferredLocation.id, type: preferredLocation.type });
}
}
inventoryData.policies = {
orderTracking: policies.orderTracking || inventoryData.policies.orderTracking,
alerting: {
lowestStockLevel:
policies.alerting && policies.alerting.lowestStockLevel !== undefined
? policies.alerting.lowestStockLevel
: inventoryData.policies.alerting.lowestStockLevel,
highestStockLevel:
policies.alerting && policies.alerting.highestStockLevel !== undefined
? policies.alerting.highestStockLevel
: inventoryData.policies.alerting.highestStockLevel,
alertStockLevel:
policies.alerting && policies.alerting.alertStockLevel !== undefined
? policies.alerting.alertStockLevel
: inventoryData.policies.alerting.alertStockLevel,
reOrderLevel:
policies.alerting && policies.alerting.reOrderLevel !== undefined
? policies.alerting.reOrderLevel
: inventoryData.policies.alerting.reOrderLevel,
},
replenishment: policies.replenishment || inventoryData.policies.replenishment,
preferredLocations: preferredLocations,
};
if (!inventory) {
res.status(400).send("Inventory not found");
return;
}
let { name, policies, widgetName, icon_slug } = req.body;
if (name) {
inventory.name = name;
}
await inventoryData.save();
res.send({ success: true, data: inventoryData });
if (widgetName) {
inventory.widgetName = widgetName;
}
if (icon_slug) {
inventory.icon_slug = icon_slug;
}
if (!policies) {
policies = {};
}
// const widgetFamilyData = createWidgetFamiliesData(inventory, widgetFamily);
const verifiedPolicies = {
orderTracking: policies.orderTracking !== undefined ? policies.orderTracking : inventory.policies.orderTracking,
alerting: policies.alerting !== undefined ? policies.alerting : inventory.policies.alerting,
replenishment: policies.replenishment !== undefined ? policies.replenishment : inventory.policies.replenishment,
preferredLocations: policies.replenishment !== undefined ? policies.replenishment : inventory.policies.preferredLocations,
inventory_process: policies.inventory_process || inventory.policies.inventory_process,
};
inventory.policies = verifiedPolicies;
await inventory.save();
res.send({ success: true, data: { inventory } });
} catch (error) {
next(error);
}

View File

@@ -1,6 +1,5 @@
const router = require("express").Router();
const controller = require("./inventory.controller");
/**
* @route /inventory/
*/
@@ -16,6 +15,11 @@ router.patch("/:id", controller.updateInventoryByID);
*/
router.get("/types", controller.getInventoryTypes);
/**
* @route /inventory/all
*/
router.get("/all", controller.getInventories);
/**
* @route /inventory/filter-by-type
*/

View File

@@ -1,7 +1,6 @@
const mongoose = require("mongoose");
const Item = require("../models/Item");
const WidgetFamily = require("../models/WidgetFamily");
const Inventory = require("../models/Inventory");
const {
PickItemTransaction,
PutItemTransaction,
@@ -11,11 +10,11 @@ const {
ReportItemTransaction,
AdjustItemTransaction,
} = require("../models/ItemTransaction");
const { S3 } = require("./../config/aws");
const ItemAssociation = require("../models/ItemAssociation");
const Sublevel = require("../models/Sublevel");
const { InventoryTypes, ReportItemForTypes } = require("../config/constants");
const { ReportItemForTypes } = require("../config/constants");
const { filterItems, filterItemAssociations } = require("./utils/aggregation");
module.exports = {
/**
* Gets the Item data by `id`
@@ -24,16 +23,22 @@ module.exports = {
const { id } = req.params;
if (!id) {
res.status(400).send("Missing id param");
res.status(400).send({ success: false, error: "Missing id param" });
return;
}
try {
const itemData = await Item.findById(id);
const itemData = await Item.findById(id).populate({ path: "widgetFamily", populate: "inventory" });
if (!itemData) {
res.status(404);
res.status(404).send({ success: false, error: "Item not found" });
return;
}
if (itemData.images && itemData.images.length > 0) {
itemData.images = itemData.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
res.send({ success: true, data: itemData });
} catch (error) {
next(error);
@@ -63,9 +68,16 @@ module.exports = {
countPerPalletPackage: req.body.countPerPalletPackage,
customAttributes: req.body.customAttributes,
widgetFamily: widgetFamily,
policiesMetadata: {
underStockLevelCount: req.body.policiesMetadata.underStockLevelCount,
overStockLevelCount: req.body.policiesMetadata.overStockLevelCount,
alertStockLevelCount: req.body.policiesMetadata.alertStockLevelCount,
reorderStockLevelCount: req.body.policiesMetadata.reorderStockLevelCount,
},
};
for (const key of Object.keys(item)) {
if (["customAttributes"].includes(key)) continue;
if (item[key] === undefined) {
res.status(400).send({ success: false, error: `Missing required param: "${key}"` });
return;
@@ -80,6 +92,24 @@ module.exports = {
res.status(404);
return;
}
const images = req.files;
for (let i = 0; i < images.length; i++) {
const url = await S3.uploadFile(
`item/${itemData._id.toString()}-${Date.now()}-${i}.${images[i].originalname.split(".").slice(-1).pop()}`,
images[i].path
);
itemData.images ||= [];
itemData.images.push({ url });
}
itemData.save();
if (itemData.images) {
itemData.images = itemData.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
res.send({ success: true, data: itemData });
} catch (error) {
next(error);
@@ -130,8 +160,32 @@ module.exports = {
itemData[key] = item[key];
}
}
// Removal of images
const existingImageIds = req.body.imageIds || [];
itemData.images = itemData.images.filter((image) => {
if (!image) return false;
return existingImageIds.includes(image._id.toString());
});
// Addition of images
const images = req.files;
if (images && Array.isArray(images)) {
for (let i = 0; i < images.length; i++) {
const url = await S3.uploadFile(
`item/${itemData._id.toString()}-${Date.now()}-${i}.${images[i].originalname.split(".").slice(-1).pop()}`,
images[i].path
);
itemData.images ||= [];
itemData.images.push({ url });
}
}
await itemData.save();
if (itemData.images) {
itemData.images = itemData.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
res.send({ success: true, data: itemData });
} catch (error) {
next(error);
@@ -142,75 +196,39 @@ module.exports = {
* Gets the Items data by filter
*/
getItemsByFilter: async (req, res, next) => {
let { family, type, inventory, page, perPage } = req.query;
let { family, inventory, page, perPage } = req.query;
page = page ? parseInt(page) : 0;
perPage = perPage ? parseInt(perPage) : 10;
const inventoryFilters = {};
let inventories;
let widgetFamilies;
let itemFilters;
try {
if (type && InventoryTypes.includes(type)) {
inventoryFilters["type"] = type;
}
const results = await filterItems(inventory, family, page, perPage);
if (inventory) {
inventoryFilters["_id"] = inventory;
for (const item of results[0].result) {
if (item.images) {
item.images = item.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
}
res.send({ success: true, data: results[0] });
} catch (error) {
next(error);
}
},
getItemAssociationsByFilter: async (req, res, next) => {
let { family, inventory, page, perPage } = req.query;
page = page ? parseInt(page) : 0;
perPage = perPage ? parseInt(perPage) : 10;
try {
const results = await filterItemAssociations(inventory, family, page, perPage);
if (Object.keys(inventoryFilters).length > 0) {
inventories = await Inventory.find(inventoryFilters);
for (const item of results[0].result) {
if (item.images) {
item.images = item.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
}
const widgetFamilyFilters = [];
if (inventories) {
widgetFamilyFilters.push({
inventory: { $in: inventories.map((_) => _._id) },
});
}
if (family) {
widgetFamilyFilters.push({
name: family,
});
}
if (widgetFamilyFilters.length > 0) {
widgetFamilies = await WidgetFamily.find({
$or: widgetFamilyFilters,
});
}
if (widgetFamilies) {
itemFilters = { widgetFamily: { $in: widgetFamilies.map((_) => _._id) } };
} else {
itemFilters = {};
}
const itemData = await Item.find(
itemFilters,
{
id: 1,
commonName: 1,
formalName: 1,
description: 1,
manufacturer: 1,
widgetFamily: 1,
size: 1,
color: 1,
type: 1,
unitOfMaterial: 1,
unitCost: 1,
packageCount: 1,
countPerPallet: 1,
countPerPalletPackage: 1,
customAttributes: 1,
},
{ skip: page * perPage, limit: perPage }
).populate({ path: "widgetFamily" });
if (!itemData) {
res.status(404);
return;
}
res.send({ success: true, data: itemData });
res.send({ success: true, data: results[0] });
} catch (error) {
next(error);
}
@@ -229,7 +247,7 @@ module.exports = {
return;
}
const { putQuantity, subLevel } = req.body;
const { putQuantity, subLevel, usageReason, job } = req.body;
if (!(putQuantity && putQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
res.status(400).send("Invalid value for putQuantity/subLevel");
return;
@@ -250,6 +268,8 @@ module.exports = {
performedBy: res.locals.user,
putQuantity: putQuantity,
subLevel: subLevelObj,
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemAssociation, itemTransaction } });
res.send({ success: true, data: { itemAssociation, itemTransaction } });
@@ -272,7 +292,7 @@ module.exports = {
return;
}
const { pickupQuantity, subLevel } = req.body;
const { pickupQuantity, subLevel, usageReason, job } = req.body;
if (!(pickupQuantity && pickupQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
res.status(400).send("Invalid value for pickupQuantity/subLevel");
return;
@@ -293,6 +313,8 @@ module.exports = {
performedBy: res.locals.user,
pickupQuantity: pickupQuantity,
subLevel: subLevelObj,
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemAssociation, itemTransaction } });
} catch (error) {
@@ -314,15 +336,15 @@ module.exports = {
return;
}
const { reserveQuantity, job, pickupDate } = req.body;
if (!(reserveQuantity && reserveQuantity > 0) || !(job && mongoose.isValidObjectId(job)) || !(pickupDate && Date.parse(pickupDate))) {
res.status(400).send("Invalid value for reserveQuantity/job/pickupDate");
const { reserveQuantity, job, pickupDate, usageReason } = req.body;
if (!(reserveQuantity && reserveQuantity > 0) || !(job && mongoose.isValidObjectId(job))) {
res.status(400).send("Invalid value for reserveQuantity/job");
return;
}
const itemAssociation = await ItemAssociation.findOne({ item_id: item._id, availableQuantity: { $gte: reserveQuantity } });
if (!itemAssociation) {
res.status(500).send("Quantity unavailable");
res.status(500).send({ success: false, error: "Quantity unavailable" });
return;
}
itemAssociation.reservedQuantity = itemAssociation.reservedQuantity + reserveQuantity;
@@ -335,7 +357,8 @@ module.exports = {
performedBy: res.locals.user,
reserveQuantity: reserveQuantity,
job: job,
pickupDate: Date.parse(pickupDate),
pickupDate: pickupDate ? Date.parse(pickupDate) : undefined,
usageReason: usageReason ? usageReason : "",
});
res.send({ success: true, data: { itemAssociation, itemTransaction } });
} catch (error) {
@@ -357,19 +380,17 @@ module.exports = {
return;
}
const { checkInMeterReading, hasIssue, issueDescription } = req.body;
if (!(checkInMeterReading && checkInMeterReading > 0)) {
res.status(400).send("Invalid value for checkInMeterReading");
return;
}
const { checkInMeterReading, hasIssue, issueDescription, usageReason, job } = req.body;
const itemTransaction = await CheckInItemTransaction.create({
type: "CHECK-IN",
performedOn: item,
performedBy: res.locals.user,
checkInMeterReading: checkInMeterReading,
hasIssue: hasIssue,
hasIssue: hasIssue || false,
issueDescription: hasIssue ? issueDescription : "",
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemTransaction } });
} catch (error) {
@@ -391,19 +412,15 @@ module.exports = {
return;
}
const { checkOutMeterReading, job, usageReason } = req.body;
if (!(checkOutMeterReading && checkOutMeterReading > 0) || !(job && mongoose.isValidObjectId(job))) {
res.status(400).send("Invalid value for checkOutMeterReading/job");
return;
}
const { checkOutMeterReading, usageReason, job } = req.body;
const itemTransaction = await CheckOutItemTransaction.create({
type: "CHECK-OUT",
performedOn: item,
performedBy: res.locals.user,
checkOutMeterReading: checkOutMeterReading,
job: job,
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemTransaction } });
} catch (error) {
@@ -425,7 +442,7 @@ module.exports = {
return;
}
const { reportingFor, details } = req.body;
const { reportingFor, details, usageReason, job } = req.body;
if (!(reportingFor && ReportItemForTypes.includes(reportingFor))) {
res.status(400).send("Invalid value for checkOutMeterReading/job");
return;
@@ -437,6 +454,8 @@ module.exports = {
performedBy: res.locals.user,
reportingFor: reportingFor,
details: details ? details : "",
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemTransaction } });
} catch (error) {
@@ -458,7 +477,7 @@ module.exports = {
return;
}
const { recountedQuantity, damagedQuantity, subLevel } = req.body;
const { recountedQuantity, damagedQuantity, subLevel, usageReason, job } = req.body;
if (!(recountedQuantity && recountedQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
res.status(400).send("Invalid value for pickupQuantity/subLevel");
return;
@@ -483,10 +502,64 @@ module.exports = {
damagedQuantity,
totalAdjustment,
newAdjustedQuantity: itemAssociation.totalQuantity,
usageReason: usageReason ? usageReason : "",
job: job,
});
res.send({ success: true, data: { itemAssociation, itemTransaction } });
} catch (error) {
next(error);
}
},
addImageToItem: async (req, res, next) => {
const { id } = req.params;
if (!mongoose.isValidObjectId(id)) {
res.status(400).send({ success: false, error: "invalid id" });
}
const item = await Item.findById(id);
if (!item) {
res.status(404).send({ success: false, error: "item not found" });
}
const image = req.file;
const url = await S3.uploadFile(`item/${item._id.toString()}-${Date.now()}-0.${image.originalname.split(".").slice(-1).pop()}`, image.path);
item.images ||= [];
item.images.push({ url });
await item.save();
if (item.images) {
item.images = item.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
res.send({ success: true, data: item });
},
removeImageFromItem: async (req, res, next) => {
const { id, image_id } = req.params;
if (!mongoose.isValidObjectId(id)) {
res.status(400).send({ success: false, error: "invalid id" });
}
if (!mongoose.isValidObjectId(image_id)) {
res.status(400).send({ success: false, error: "invalid image_id" });
}
const item = await Item.findById(id);
if (!item) {
res.status(404).send({ success: false, error: "item not found" });
}
item.images = item.images.filter((itemImage) => {
return itemImage._id.toString() != image_id;
});
await item.save();
if (item.images) {
item.images = item.images.map((_) => {
return { _id: _._id, url: S3.generatePresignedUrl(_.url) };
});
}
res.send({ success: true, data: item });
},
};

View File

@@ -1,21 +1,39 @@
const router = require("express").Router();
const controller = require("./item.controller");
const { AuthenticateMiddleware, ItemTransactionCheck } = require("./utils/authorize");
/**
* @route /item/
*/
router.post("/", controller.createItem);
const multer = require("multer");
const upload = multer({ dest: "tmp/uploads/" });
/**
* @route /item/
*/
router.patch("/:id", controller.updateItemByID);
router.post("/", upload.any("images"), controller.createItem);
/**
* @route /item/:id/image
*/
router.post("/:id/image", upload.single("image"), controller.addImageToItem);
/**
* @route /item/:id/image/:image_id
*/
router.delete("/:id/image/:image_id", controller.removeImageFromItem);
/**
* @route /item/
*/
router.patch("/:id", upload.any("images"), controller.updateItemByID);
/**
* @route /item/filter
*/
router.get("/filter", controller.getItemsByFilter);
/**
* @route /item/filter-association
*/
router.get("/filter-association", controller.getItemAssociationsByFilter);
/**
* @route /item/:id
*/

View File

@@ -1,8 +1,13 @@
const Sublevel = require("../models/Sublevel");
const mongoose = require("mongoose");
const { filterSublevels } = require("./utils/aggregation");
const { addSublevelToParent, deleteSubLevelTreeFromRoot, validPositions } = require("./utils/sublevel");
const { SubLevelTypes } = require("../config/constants");
const getLabelTerm = (name) => {
return name.toString().slice(name.toString().length - 3);
};
module.exports = {
/**
* Gets the sublevel data by `id`
@@ -31,6 +36,7 @@ module.exports = {
* Create a sublevel
*/
createSubLevel: async (req, res, next) => {
console.log(req.body);
const { name, type, specs, parent_id, parentIsLevel, positions } = req.body;
if (!(name && type && parent_id && positions)) {
@@ -70,7 +76,7 @@ module.exports = {
res.status(404);
return;
}
req.send(sublevelData);
res.send({ ...sublevelData?._doc, positions });
} catch (error) {
next(error);
}
@@ -182,4 +188,44 @@ module.exports = {
next(error);
}
},
filterSublevels: async (req, res, next) => {
const { warehouse, zone, area, row } = req.body;
const results = await filterSublevels({ warehouse, zone, area, row });
const resultsGroupedByBay = {};
for (const resultItem of results) {
if (resultsGroupedByBay[resultItem.bay._id]) {
resultsGroupedByBay[resultItem.bay._id].location_data.push({
level: { id: resultItem.level._id, name: resultItem.level.name },
sublevel: { id: resultItem.sublevel1._id, name: resultItem.sublevel1.name },
label: `Z${getLabelTerm(resultItem.zone._id)}-A${getLabelTerm(resultItem.area._id)}-R${getLabelTerm(resultItem.row._id)}-B${getLabelTerm(
resultItem.bay._id
)}-L${getLabelTerm(resultItem.level._id)}-${resultItem.sublevel1.name}`,
});
} else {
resultsGroupedByBay[resultItem.bay._id] = {
warehouse: { id: resultItem._id, name: resultItem.name },
zone: { id: resultItem.zone._id, name: resultItem.area.name },
area: { id: resultItem.area._id, name: resultItem.area.name },
row: { id: resultItem.row._id, name: resultItem.row.name },
bay: { id: resultItem.bay._id, name: resultItem.bay.name },
totem_label: [
`Z${getLabelTerm(resultItem.zone._id)}-A${getLabelTerm(resultItem.area._id)}-R${getLabelTerm(resultItem.row._id)}-B${getLabelTerm(
resultItem.bay._id
)}}`,
],
location_data: [
{
level: { id: resultItem.level._id, name: resultItem.level.name },
sublevel: { id: resultItem.sublevel1._id, name: resultItem.sublevel1.name },
label: `Z${getLabelTerm(resultItem.zone._id)}-A${getLabelTerm(resultItem.area._id)}-R${getLabelTerm(
resultItem.row._id
)}-B${getLabelTerm(resultItem.bay._id)}-L${getLabelTerm(resultItem.level._id)}-${resultItem.sublevel1.name}`,
},
],
};
}
}
res.send({ success: true, data: Object.values(resultsGroupedByBay) });
},
};

View File

@@ -1,6 +1,10 @@
const router = require("express").Router();
const controller = require("./sublevel.controller");
/**
* @route /sublevel/filter
*/
router.post("/filter", controller.filterSublevels);
/**
* @route /sublevel/

View File

@@ -1,12 +1,12 @@
const bcrypt = require("bcrypt");
const jwt = require("jsonwebtoken");
const mongoose = require("mongoose");
const User = require("./../models/User");
const { JWT_SECRET, JWT_REFRESH_EXPIRY_TIME, JWT_ACCESS_EXPIRY_TIME } = require("./../config/env");
const UserRole = require("../models/UserRole");
const UserPermission = require("../models/UserPermission");
const { AllUIModules } = require("../config/constants");
const { getScopes } = require("./utils/access-control");
const { InventoryScopes, WarehouseScopes, UserActions, AllUIModules } = require("./../config/constants");
const { S3 } = require("./../config/aws");
const createAccessToken = (id) => {
return jwt.sign({ id }, JWT_SECRET, {
@@ -89,48 +89,86 @@ module.exports = {
addUserAccessControl: async (req, res, next) => {
const { user } = req.params;
const { roles, permissions } = req.body;
const {
roles,
permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules },
} = req.body;
if (!mongoose.isValidObjectId(user)) {
throw new Error(`invalid format for user id field`);
}
const userObject = await User.findById(user);
if (!userObject) {
res.status(404).send({ success: false, error: "User not found" });
}
let verifiedRoleIds = await getValidIds(roles, UserRole);
let verifiedPermissionIds = await getValidIds(permissions, UserPermission);
verifiedRoleIds = verifiedRoleIds || [];
verifiedPermissionIds = verifiedPermissionIds || [];
if (roles) {
let verifiedRoleIds = await getValidIds(roles, UserRole);
verifiedRoleIds = verifiedRoleIds || [];
userObject.roles = Array.from(new Set([...userObject.roles, ...verifiedRoleIds]));
}
const response = await User.findByIdAndUpdate(
user,
{
$push: {
roles: { $each: verifiedRoleIds },
permissions: { $each: verifiedPermissionIds },
},
},
{ returnDocument: "after" }
);
res.send({ success: true, data: response });
if (inventoryScopes) {
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
userObject.permissions.inventoryScopes = Array.from(new Set([...userObject.permissions.inventoryScopes, ...verifiedInventoryScopes]));
}
if (warehouseScopes) {
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
userObject.permissions.warehouseScopes = Array.from(new Set([...userObject.permissions.warehouseScopes, ...verifiedWarehouseScopes]));
}
if (actions) {
userObject.permissions.actions = Array.from(new Set([...userObject.permissions.actions, ...actions.filter((_) => UserActions.includes(_))]));
}
if (allowedUIModules) {
userObject.permissions.allowedUIModules = Array.from(
new Set([...userObject.permissions.allowedUIModules, ...allowedUIModules.filter((_) => AllUIModules.includes(_))])
);
}
await userObject.save();
res.send({ success: true, data: userObject });
},
removeUserAccessControl: async (req, res, next) => {
const { user } = req.params;
const { roles, permissions } = req.body;
const {
roles,
permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules },
} = req.body;
if (!mongoose.isValidObjectId(user)) {
throw new Error(`invalid format for user id field`);
}
const verifiedRoleIds = await getValidIds(roles, UserRole);
const verifiedPermissionIds = await getValidIds(permissions, UserPermission);
const response = await User.findByIdAndUpdate(
user,
{
$pull: {
roles: { $in: verifiedRoleIds },
permissions: { $in: verifiedPermissionIds },
},
},
{ returnDocument: "after" }
);
res.send({ success: true, data: response });
const userObject = await User.findById(user);
if (!userObject) {
res.status(404).send({ success: false, error: "User not found" });
}
if (roles) {
let verifiedRoleIds = await getValidIds(roles, UserRole);
verifiedRoleIds = verifiedRoleIds || [];
userObject.roles = userObject.roles.filter((_) => !verifiedRoleIds.includes(_));
}
if (inventoryScopes) {
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
userObject.permissions.inventoryScopes = userObject.permissions.inventoryScopes.filter((_) => !verifiedInventoryScopes.includes(_.id));
}
if (warehouseScopes) {
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
userObject.permissions.warehouseScopes = userObject.permissions.warehouseScopes.filter((warehouseScope) => {
for (const verifiedWarehouseScope of verifiedWarehouseScopes) {
if (verifiedWarehouseScope.id == warehouseScope.id && verifiedWarehouseScope.type == warehouseScope.type) {
return false;
}
}
});
}
if (actions) {
userObject.permissions.actions = userObject.permissions.actions.filter((_) => !actions.includes(_));
}
if (allowedUIModules) {
userObject.permissions.allowedUIModules = userObject.permissions.allowedUIModules.filter((_) => !allowedUIModules.includes(_));
}
await userObject.save();
res.send({ success: true, data: userObject });
},
getUIAccessControl: async (req, res, next) => {
@@ -167,9 +205,11 @@ module.exports = {
const result = await User.find()
.skip(page * perPage)
.limit(perPage)
.populate({ path: "roles", populate: "permissions" })
.populate("permissions")
.populate("roles")
.populate("createdBy");
for (const user of result) {
if (user.image_url) user.image_url = S3.generatePresignedUrl(user.image_url);
}
res.send({ success: true, data: result });
} catch (error) {
next(error);
@@ -183,17 +223,20 @@ module.exports = {
}
try {
const result = await User.findOne({ _id: id }, { id: 1, fullName: 1, email: 1, roles: 1, permissions: 1, createdBy: 1 })
.populate({ path: "roles", populate: "permissions" })
.populate("permissions")
const result = await User.findOne({ _id: id })
.populate("roles")
.populate("createdBy");
if (result._doc.image_url) result.image_url = S3.generatePresignedUrl(result.image_url);
res.send({ success: true, data: result });
} catch (error) {
next(error);
}
},
createUser: async (req, res, next) => {
const { email, fullName, password } = req.body;
let { email, fullName, password, roles, permissions } = req.body;
permissions ||= {};
const { inventoryScopes, warehouseScopes, actions, allowedUIModules } = permissions;
try {
const salt = await bcrypt.genSalt();
const newUser = {
@@ -203,10 +246,36 @@ module.exports = {
createdBy: res.locals.user,
};
if (roles) {
let verifiedRoleIds = await getValidIds(roles, UserRole);
verifiedRoleIds = verifiedRoleIds || [];
newUser.roles = verifiedRoleIds;
}
newUser.permissions = {};
if (inventoryScopes) {
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
newUser.permissions.inventoryScopes = verifiedInventoryScopes;
}
if (warehouseScopes) {
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
newUser.permissions.warehouseScopes = verifiedWarehouseScopes;
}
if (actions) {
newUser.permissions.actions = actions.filter((_) => UserActions.includes(_));
}
if (allowedUIModules) {
newUser.permissions.allowedUIModules = allowedUIModules.filter((_) => AllUIModules.includes(_));
}
const user = await User.create(newUser);
console.log({ msg: "new user created", user });
res.send({ success: true, data: user });
const image = req.file;
if (image) {
const url = await S3.uploadFile(`user/${user._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, image.path);
user.image_url = url;
await user.save();
}
res.send({ success: true, data: { ...user, image_url: S3.generatePresignedUrl(user.image_url) } });
} catch (err) {
console.log(err);
next(err);
@@ -219,7 +288,9 @@ module.exports = {
return;
}
const { email, fullName, password } = req.body;
let { email, fullName, password, roles, permissions } = req.body;
permissions ||= {};
const { inventoryScopes, warehouseScopes, actions, allowedUIModules } = permissions;
try {
const user = await User.findById(id);
if (!user) {
@@ -231,10 +302,39 @@ module.exports = {
if (email) user.email = email;
if (fullName) user.fullName = fullName;
if (password) user.password = await bcrypt.hash(password, salt);
if (roles) {
let verifiedRoleIds = await getValidIds(roles, UserRole);
verifiedRoleIds = verifiedRoleIds || [];
user.roles = verifiedRoleIds;
}
if (inventoryScopes) {
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
user.permissions.inventoryScopes = verifiedInventoryScopes;
user.markModified("permissions.inventoryScopes");
}
if (warehouseScopes) {
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
user.permissions.warehouseScopes = verifiedWarehouseScopes;
user.markModified("permissions.warehouseScopes");
}
if (actions) {
user.permissions.actions = actions.filter((_) => UserActions.includes(_));
user.markModified("permissions.actions");
}
if (allowedUIModules) {
user.permissions.allowedUIModules = allowedUIModules.filter((_) => AllUIModules.includes(_));
user.markModified("permissions.allowedUIModules");
}
const image = req.file;
if (image) {
const url = await S3.uploadFile(`user/${user._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, image.path);
user.image_url = url;
}
await user.save();
res.send({ success: true, data: user });
res.send({ success: true, data: { ...user, image_url: S3.generatePresignedUrl(user.image_url) } });
} catch (err) {
console.log(err);
next(err);

View File

@@ -1,6 +1,9 @@
const router = require("express").Router();
const controller = require("./user.controller");
const { SuperAdminCheck, AuthenticateMiddleware } = require("./utils/authorize");
const multer = require("multer");
const upload = multer({ dest: "tmp/uploads/" });
router.post("/register", controller.registerUser);
router.post("/login", controller.loginUser);
@@ -11,7 +14,7 @@ router.get("/allowed-ui-modules", AuthenticateMiddleware, controller.getUIAccess
router.get("/all", AuthenticateMiddleware, SuperAdminCheck, controller.getAllUsers);
router.get("/:id", AuthenticateMiddleware, SuperAdminCheck, controller.getUserById);
router.post("/create", AuthenticateMiddleware, SuperAdminCheck, controller.createUser);
router.post("/:id", AuthenticateMiddleware, SuperAdminCheck, controller.updateUser);
router.post("/create", upload.single("image"), AuthenticateMiddleware, SuperAdminCheck, controller.createUser);
router.post("/:id", upload.single("image"), AuthenticateMiddleware, SuperAdminCheck, controller.updateUser);
module.exports = router;

View File

@@ -1,15 +1,7 @@
const mongoose = require("mongoose");
const UserRole = require("../models/UserRole");
const UserPermission = require("../models/UserPermission");
const getValidPermissions = async (permissions) => {
const verifiedPermissions = permissions.filter((permission) => mongoose.isValidObjectId(permission));
if (verifiedPermissions.length === 0) return [];
const permissionObjects = await UserPermission.find({
_id: { $in: verifiedPermissions },
}).select({ _id: 1 });
return permissionObjects.map((_) => _._id);
};
const { getScopes } = require("./utils/access-control");
const { InventoryScopes, WarehouseScopes, UserActions, AllUIModules } = require("./../config/constants");
module.exports = {
getAllRoles: async (req, res, next) => {
@@ -43,11 +35,22 @@ module.exports = {
},
createRole: async (req, res, next) => {
try {
const { name, permissions } = req.body;
const verifiedPermissions = await getValidPermissions(permissions);
const {
name,
permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules },
status,
} = req.body;
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
const newUserRole = await UserRole.create({
name,
permissions: verifiedPermissions,
status,
permissions: {
inventoryScopes: verifiedInventoryScopes,
warehouseScopes: verifiedWarehouseScopes,
actions: actions == undefined ? [] : actions.filter((_) => UserActions.includes(_)),
allowedUIModules: allowedUIModules == undefined ? [] : allowedUIModules.filter((_) => AllUIModules.includes(_)),
},
});
res.send({ success: true, data: newUserRole });
} catch (e) {
@@ -55,8 +58,48 @@ module.exports = {
}
},
updateRole: async (req, res, next) => {
// Need more clarity
res.send({ success: false, error: "not implemented" });
try {
const { id } = req.params;
if (!(id && mongoose.isValidObjectId(id))) {
res.status(400).send({ success: false, error: "invalid Id params" });
}
const role = await UserRole.findById(id);
if (!role) {
res.status(404).send({ success: false, error: "Role not found" });
}
const {
name,
permissions: { inventoryScopes, warehouseScopes, actions, allowedUIModules },
} = req.body;
if (name) {
role.name = name;
role.markModified("name");
}
if (inventoryScopes) {
const verifiedInventoryScopes = await getScopes(inventoryScopes, InventoryScopes);
role.permissions.inventoryScopes = verifiedInventoryScopes;
role.markModified("permissions.inventoryScopes");
}
if (warehouseScopes) {
const verifiedWarehouseScopes = await getScopes(warehouseScopes, WarehouseScopes);
role.permissions.warehouseScopes = verifiedWarehouseScopes;
role.markModified("permissions.warehouseScopes");
}
if (actions) {
role.permissions.actions = actions.filter((_) => UserActions.includes(_));
role.markModified("permissions.actions");
}
if (allowedUIModules) {
role.permissions.allowedUIModules = allowedUIModules.filter((_) => AllUIModules.includes(_));
role.markModified("permissions.allowedUIModules");
}
await role.save();
res.send({ success: true, data: role });
} catch (e) {
next(e);
}
},
deleteRole: async (req, res, next) => {
try {

View File

@@ -0,0 +1,27 @@
const mongoose = require("mongoose");
module.exports = {
getScopes: async (scopes, searchSet) => {
const verifiedScopes = [];
if (scopes !== undefined && Array.isArray(scopes)) {
for (const scope of scopes) {
if (mongoose.isValidObjectId(scope.id)) {
if (scope.type !== undefined && searchSet.includes(scope.type)) {
const model = require(`../../models/${scope.type}`);
const inventoryObject = await model.findById(scope.id);
if (inventoryObject == undefined) {
continue;
}
verifiedScopes.push({
id: inventoryObject._id,
type: scope.type,
});
}
} else {
throw new Error(`invalid data format for object-id - ${scope.id}`);
}
}
}
return verifiedScopes;
},
};

View File

@@ -0,0 +1,241 @@
const mongoose = require("mongoose");
const Item = require("../../models/Item");
const Warehouse = require("./../../models/Warehouse");
const lookupPipeline = (from, localField, foreignField, as) => {
return {
$lookup: {
from,
localField,
foreignField,
as,
},
};
};
const unwindPipeline = (path, preserveNullAndEmptyArrays = true) => {
return {
$unwind: {
path: "$" + path,
preserveNullAndEmptyArrays,
},
};
};
const sublevelFilterAggregationPipeline = (warehouse, zone, area, row) => {
const sublevelFilterAggregationPipeline = [];
if (warehouse && mongoose.isValidObjectId(warehouse)) {
sublevelFilterAggregationPipeline.push({
$match: {
_id: mongoose.Types.ObjectId(warehouse),
},
});
}
sublevelFilterAggregationPipeline.push(lookupPipeline("zones", "_id", "warehouse_id", "zone"));
sublevelFilterAggregationPipeline.push(unwindPipeline("zone"));
if (warehouse && zone && mongoose.isValidObjectId(zone)) {
sublevelFilterAggregationPipeline.push({
$match: {
"zone._id": mongoose.Types.ObjectId(zone),
},
});
}
sublevelFilterAggregationPipeline.push(lookupPipeline("areas", "zone._id", "zone_id", "area"));
sublevelFilterAggregationPipeline.push(unwindPipeline("area"));
if (warehouse && zone && area && mongoose.isValidObjectId(area)) {
sublevelFilterAggregationPipeline.push({
$match: {
"area._id": mongoose.Types.ObjectId(area),
},
});
}
sublevelFilterAggregationPipeline.push(lookupPipeline("rows", "area._id", "area_id", "row"));
sublevelFilterAggregationPipeline.push(unwindPipeline("row"));
if (warehouse && zone && area && row && mongoose.isValidObjectId(row)) {
sublevelFilterAggregationPipeline.push({
$match: {
"row._id": mongoose.Types.ObjectId(row),
},
});
}
sublevelFilterAggregationPipeline.push(lookupPipeline("bays", "row._id", "row_id", "bay"));
sublevelFilterAggregationPipeline.push(unwindPipeline("bay"));
sublevelFilterAggregationPipeline.push(lookupPipeline("levels", "bay._id", "bay_id", "level"));
sublevelFilterAggregationPipeline.push(unwindPipeline("level"));
sublevelFilterAggregationPipeline.push(lookupPipeline("sublevels", "level._id", "main_level_id", "sublevel1"));
sublevelFilterAggregationPipeline.push(unwindPipeline("sublevel1"));
// sublevelFilterAggregationPipeline.push(lookupPipeline("sublevels", "sublevel1._id", "parent_id", "sublevel2"));
// sublevelFilterAggregationPipeline.push(unwindPipeline("sublevel2", false));
// sublevelFilterAggregationPipeline.push(lookupPipeline("sublevels", "sublevel2._id", "parent_id", "sublevel3"));
// sublevelFilterAggregationPipeline.push(unwindPipeline("sublevel3", false));
return sublevelFilterAggregationPipeline;
};
const itemAssociationFilterAggregationPipeline = (inventory, widgetFamily, page, perPage, showAllItems) => {
const itemFilterAggregationPipeline = [];
itemFilterAggregationPipeline.push(lookupPipeline("widgetfamilies", "widgetFamily", "_id", "widgetfamily"));
itemFilterAggregationPipeline.push(unwindPipeline("widgetfamily"));
const matchQuery = {};
if (inventory) {
matchQuery["widgetfamily.inventory"] = mongoose.Types.ObjectId(inventory);
}
if (widgetFamily) {
matchQuery["widgetfamily._id"] = mongoose.Types.ObjectId(widgetFamily);
}
if (Object.values(matchQuery).length > 0) {
itemFilterAggregationPipeline.push({
$match: matchQuery,
});
}
itemFilterAggregationPipeline.push(lookupPipeline("inventories", "widgetfamily.inventory", "_id", "inventory"));
itemFilterAggregationPipeline.push(unwindPipeline("inventory"));
itemFilterAggregationPipeline.push(lookupPipeline("itemassociations", "_id", "item_id", "association"));
itemFilterAggregationPipeline.push(unwindPipeline("association", false));
itemFilterAggregationPipeline.push(lookupPipeline("sublevels", "association.sub_level_id", "_id", "location"));
itemFilterAggregationPipeline.push(unwindPipeline("location"));
itemFilterAggregationPipeline.push({
$project: {
_id: 1,
commonName: 1,
formalName: 1,
description: 1,
manufacturer: 1,
size: 1,
color: 1,
type: 1,
unitOfMaterial: 1,
unitCost: 1,
packageCount: 1,
countPerPallet: 1,
countPerPalletPackage: 1,
customAttributes: 1,
policiesMetadata: 1,
createdAt: 1,
updatedAt: 1,
images: 1,
widgetfamily: 1,
inventory: 1,
location: 1,
totalQuantity: "$association.totalQuantity",
reservedQuantity: "$association.reservedQuantity",
availableQuantity: "$association.availableQuantity",
},
});
itemFilterAggregationPipeline.push({
$facet: {
result: [
{
$skip: page * perPage,
},
{
$limit: perPage,
},
],
count: [
{
$count: "Total",
},
],
},
});
itemFilterAggregationPipeline.push({
$project: {
result: 1,
count: {
$first: "$count.Total",
},
},
});
return itemFilterAggregationPipeline;
};
const itemFilterAggregationPipeline = (inventory, widgetFamily, page, perPage, showAllItems) => {
const itemFilterAggregationPipeline = [];
itemFilterAggregationPipeline.push(lookupPipeline("widgetfamilies", "widgetFamily", "_id", "widgetfamily"));
itemFilterAggregationPipeline.push(unwindPipeline("widgetfamily"));
const matchQuery = {};
if (inventory) {
matchQuery["widgetfamily.inventory"] = mongoose.Types.ObjectId(inventory);
}
if (widgetFamily) {
matchQuery["widgetfamily._id"] = mongoose.Types.ObjectId(widgetFamily);
}
if (Object.values(matchQuery).length > 0) {
itemFilterAggregationPipeline.push({
$match: matchQuery,
});
}
itemFilterAggregationPipeline.push(lookupPipeline("inventories", "widgetfamily.inventory", "_id", "inventory"));
itemFilterAggregationPipeline.push(unwindPipeline("inventory"));
itemFilterAggregationPipeline.push({
$project: {
_id: 1,
commonName: 1,
formalName: 1,
description: 1,
manufacturer: 1,
size: 1,
color: 1,
type: 1,
unitOfMaterial: 1,
unitCost: 1,
packageCount: 1,
countPerPallet: 1,
countPerPalletPackage: 1,
customAttributes: 1,
policiesMetadata: 1,
createdAt: 1,
updatedAt: 1,
images: 1,
widgetfamily: 1,
inventory: 1,
},
});
itemFilterAggregationPipeline.push({
$facet: {
result: [
{
$skip: page * perPage,
},
{
$limit: perPage,
},
],
count: [
{
$count: "Total",
},
],
},
});
itemFilterAggregationPipeline.push({
$project: {
result: 1,
count: {
$first: "$count.Total",
},
},
});
return itemFilterAggregationPipeline;
};
module.exports = {
filterSublevels: async ({ warehouse, zone, area, row }) => {
const pipeline = sublevelFilterAggregationPipeline(warehouse, zone, area, row);
return await Warehouse.aggregate(pipeline);
},
filterItems: async (inventory, widgetFamily, page, perPage, showAllItems) => {
const pipeline = itemFilterAggregationPipeline(inventory, widgetFamily, page, perPage);
return await Item.aggregate(pipeline);
},
filterItemAssociations: async (inventory, widgetFamily, page, perPage, showAllItems) => {
const pipeline = itemAssociationFilterAggregationPipeline(inventory, widgetFamily, page, perPage, showAllItems);
return await Item.aggregate(pipeline);
},
};

View File

@@ -8,15 +8,16 @@ const constants = require("../../config/constants");
const authenticate = async (token) => {
const decodedToken = jwt.verify(token, JWT_SECRET);
if (decodedToken) {
return await User.findById(decodedToken.id).populate({ path: "roles", populate: "permissions" }).populate("permissions");
return await User.findById(decodedToken.id).populate("roles");
}
};
const authorize = async (user, requiredRoles = [], requiredPermissions = []) => {
const userRoles = user.roles.map((_) => _._id);
const userPermissions = [...user.permissions.map((_) => _._id), ...userRoles.map((_) => _.permissions).flat()];
// const userRoles = user.roles.map((_) => _._id);
// const userPermissions = [...user.permissions.map((_) => _._id), ...userRoles.map((_) => _.permissions).flat()];
return user != undefined && requiredRoles.every((_) => userRoles.includes(_)) && requiredPermissions.every((_) => userPermissions.includes(_));
// return user != undefined && requiredRoles.every((_) => userRoles.includes(_)) && requiredPermissions.every((_) => userPermissions.includes(_));
return true;
};
module.exports = {

View File

@@ -58,7 +58,7 @@ const deleteSubLevelTreeFromRoot = async (root_sub_level_id) => {
const addSublevelToParent = async (payload, parent_id, parentIsLevel) => {
if (parentIsLevel) {
// add sublevel to parent
const parentData = await Sublevel.findById(parent_id);
const parentData = await Level.findById(parent_id);
parentData.sub_levels.push(payload);
return await parentData.save();
} else {

View File

@@ -1,5 +1,7 @@
const Warehouse = require("../models/Warehouse");
const mongoose = require("mongoose");
const { S3 } = require("./../config/aws");
const Inventory = require("../models/Inventory");
module.exports = {
/**
@@ -19,6 +21,9 @@ module.exports = {
res.status(404).send({ success: false, message: "not found" });
return;
}
if (warehouseData.image_url) {
warehouseData.image_url = S3.generatePresignedUrl(warehouseData.image_url);
}
res.send({ success: true, data: warehouseData });
} catch (error) {
next(error);
@@ -29,19 +34,24 @@ module.exports = {
* Create a warehouse
*/
createWarehouse: async (req, res, next) => {
const { name, address, specs, company_id } = req.body;
const { name, address, specs, company_id, preferredInventories } = req.body;
if (!(name && address)) {
res.status(400).send("Missing params param");
return;
}
let preferredInventoryObjects;
if (preferredInventories) {
preferredInventoryObjects = await Inventory.find({ _id: { $in: preferredInventories } });
}
try {
const warehouseData = new Warehouse({
name,
address,
specs,
company_id: mongoose.Types.ObjectId(company_id),
preferredInventories: preferredInventoryObjects,
});
await warehouseData.save();
@@ -49,34 +59,22 @@ module.exports = {
res.status(404).send({ success: false, message: "not found" });
return;
}
const image = req.file;
if (image) {
const url = await S3.uploadFile(`warehouse/${warehouseData._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, image.path);
warehouseData.image_url = url;
await warehouseData.save();
}
if (warehouseData.image_url) {
warehouseData.image_url = S3.generatePresignedUrl(warehouseData.image_url);
}
res.send({ success: true, message: warehouseData });
} catch (error) {
next(error);
}
},
/**
* Upload an image for the warehouse
*/
addWarehouseImage: async (req, res, next) => {
console.dir("Warehouse image uploaded:", { file: req.file });
const { id } = req.params;
try {
const warehouseDetails = await Warehouse.findById(id);
if (!warehouseDetails) {
res.send({ success: false, message: "ID not found" });
return;
}
warehouseDetails.imageUrl = req.file.path;
await warehouseDetails.save();
res.send({ success: true, data: warehouseDetails });
} catch (err) {
next(err);
}
},
/**
* Update a warehouses detail
*/
@@ -88,9 +86,9 @@ module.exports = {
return;
}
const { name, address, specs, company_id } = req.body;
if (!(name || address || specs || company_id)) {
const { name, address, specs, company_id, preferredInventories } = req.body;
const image = req.file;
if (!(name || address || specs || company_id || image)) {
res.status(400).send({ success: false, message: "Missing data in body" });
return;
}
@@ -106,8 +104,18 @@ module.exports = {
if (address) warehouseData.address = address;
if (specs) warehouseData.specs = specs;
if (company_id) warehouseData.company_id = mongoose.Types.ObjectId(company_id);
if (image) {
const url = await S3.uploadFile(`warehouseData/${warehouseData._id.toString()}.${image.originalname.split(".").slice(-1).pop()}`, image.path);
warehouseData.image_url = url;
}
if (preferredInventories) {
const preferredInventoryObjects = await Inventory.find({ _id: { $in: preferredInventories } });
warehouseData.preferredInventories = preferredInventoryObjects;
}
await warehouseData.save();
if (warehouseData.image_url) {
warehouseData.image_url = S3.generatePresignedUrl(warehouseData.image_url);
}
res.send({ success: true, data: warehouseData });
} catch (error) {
next(error);
@@ -119,6 +127,9 @@ module.exports = {
const { getAllWithPagination } = require("./utils/pagination");
const { page, perPage } = req.query;
const data = await getAllWithPagination(Warehouse, page, perPage);
for (const warehouse of data) {
if (warehouse.image_url) warehouse.image_url = S3.generatePresignedUrl(warehouse.image_url);
}
res.send({ success: true, data: data });
} catch (error) {
next(error);

View File

@@ -1,6 +1,7 @@
const router = require("express").Router();
const upload = require("../middleware/fileUpload");
const controller = require("./warehouse.controller");
const multer = require("multer");
const upload = multer({ dest: "tmp/uploads/" });
/**
* @route /warehouse/
@@ -20,16 +21,11 @@ router.get("/:id/zones", controller.getWarehouseZonesByID);
/**
* @route /warehouse/
*/
router.post("/", controller.createWarehouse);
/**
* @route /warehouse/add-image
*/
router.post("/add-image/:id", upload.single("warehouse-image"), controller.addWarehouseImage);
router.post("/", upload.single("image"), controller.createWarehouse);
/**
* @route /warehouse/
*/
router.patch("/:id", controller.updateWarehouseByID);
router.patch("/:id", upload.single("image"), controller.updateWarehouseByID);
module.exports = router;

View File

@@ -96,21 +96,21 @@ module.exports = {
const { id } = req.params;
if (!id) {
res.status(400).send("Missing id param");
res.status(400).send({ success: false, error: "Missing id param" });
return;
}
const { name, parentId, inventoryId } = req.body;
if (!(name || parentId || inventoryId)) {
res.status(400).send("Missing data in body");
res.status(400).send({ success: false, error: "Missing data in body" });
return;
}
try {
const widgetFamilyData = await WidgetFamily.findById(id);
if (!widgetFamilyData) {
res.status(404);
res.status(404).send({ success: false, error: "Widget not found" });
return;
}
@@ -122,8 +122,10 @@ module.exports = {
if (parentId && mongoose.isValidObjectId(parentId)) {
parent = await WidgetFamily.findById(parentId);
widgetFamilyData.parent = parent;
} else if (parentId && parentId == "NA") {
widgetFamilyData.parent = undefined;
} else if (parentId && !mongoose.isValidObjectId(parentId)) {
res.status(400).send("Invalid params parentId");
res.status(400).send({ success: false, error: "Invalid params parentId" });
return;
}
@@ -132,7 +134,7 @@ module.exports = {
inventory = await Inventory.findById(inventoryId);
widgetFamilyData.inventory = inventory;
} else if (inventoryId && !mongoose.isValidObjectId(inventoryId)) {
res.status(400).send("Invalid params inventoryId");
res.status(400).send({ success: false, error: "Invalid params inventoryId" });
return;
}
@@ -142,6 +144,21 @@ module.exports = {
next(error);
}
},
deleteWidgetFamilyByID: async (req, res, next) => {
const { id } = req.params;
if (!id || !mongoose.isValidObjectId(id)) {
res.status(400).send({ success: false, error: "Missing/invalid id param" });
return;
}
try {
await WidgetFamily.deleteOne({ _id: id });
res.send({ success: true });
} catch (error) {
next(error);
}
},
/**
* Gets the WidgetFamily data by `inventory`
*/

View File

@@ -26,4 +26,9 @@ router.get("/:id", controller.getWidgetFamilyByID);
*/
router.get("/:id/children", controller.getWidgetFamilyChildrenByID);
/**
* @route /widgetFamily/
*/
router.delete("/:id", controller.deleteWidgetFamilyByID);
module.exports = router;

View File

@@ -1,5 +1,4 @@
const mongoose = require("mongoose");
const { InventoryTypes, WarehouseScopes } = require("./../config/constants");
const schema = new mongoose.Schema(
{
@@ -8,49 +7,47 @@ const schema = new mongoose.Schema(
required: true,
trim: true,
},
type: {
icon_slug: {
type: String,
trim: true,
required: true,
// enum: []
},
widgetName: {
type: String,
required: true,
trim: true,
enum: InventoryTypes,
},
policies: {
orderTracking: {
type: Object, // Create a different model and reference it here once more details available
},
alerting: {
lowestStockLevel: {
type: Boolean,
required: true,
},
highestStockLevel: {
type: Boolean,
required: true,
},
alertStockLevel: {
type: Boolean,
required: true,
},
reOrderLevel: {
type: Boolean,
required: true,
},
type: Boolean,
required: true,
},
replenishment: {
type: Boolean, // Create a different model and reference it here once more details available
},
inventory_process: {
type: String,
required: true,
},
// preferredLocations: [
// {
// id: {
// type: mongoose.Schema.Types.ObjectId,
// refPath: "type",
// },
// type: {
// type: String,
// enum: WarehouseScopes,
// },
// },
// ],
preferredLocations: {
type: Object, // Create a different model and reference it here once more details available
},
preferredLocations: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
refPath: "type",
},
type: {
type: String,
enum: WarehouseScopes,
},
},
],
},
},
{

View File

@@ -1,6 +1,13 @@
const mongoose = require("mongoose");
const { CustomAttributeTypes } = require("./../config/constants");
const ItemImage = new mongoose.Schema({
url: {
type: String,
trim: true,
},
});
const schema = new mongoose.Schema(
{
commonName: {
@@ -13,6 +20,7 @@ const schema = new mongoose.Schema(
required: true,
trim: true,
},
images: [ItemImage],
description: {
type: String,
required: true,
@@ -93,6 +101,9 @@ const schema = new mongoose.Schema(
alertStockLevelCount: {
type: Number,
},
reorderStockLevelCount: {
type: Number,
},
},
},
{

View File

@@ -17,6 +17,13 @@ const schema = new mongoose.Schema(
type: mongoose.Schema.Types.ObjectId,
ref: "User",
},
usageReason: {
type: String,
trim: true,
},
job: {
type: mongoose.Schema.Types.ObjectId,
},
},
{
timestamps: true,
@@ -35,7 +42,7 @@ const PutItemTransaction = ItemTransaction.discriminator(
},
subLevel: {
type: mongoose.Schema.Types.ObjectId,
ref: "Sublevel"
ref: "Sublevel",
},
})
);
@@ -61,13 +68,8 @@ const ReserveItemTransaction = ItemTransaction.discriminator(
type: Number,
required: true,
},
job: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
pickupDate: {
type: Date,
required: true,
},
})
);
@@ -77,7 +79,6 @@ const CheckInItemTransaction = ItemTransaction.discriminator(
new mongoose.Schema({
checkInMeterReading: {
type: Number,
required: true,
},
hasIssue: {
type: Boolean,
@@ -95,15 +96,6 @@ const CheckOutItemTransaction = ItemTransaction.discriminator(
new mongoose.Schema({
checkOutMeterReading: {
type: Number,
required: true,
},
job: {
type: mongoose.Schema.Types.ObjectId,
required: true,
},
usageReason: {
type: String,
trim: true,
},
})
);

View File

@@ -1,6 +1,7 @@
const mongoose = require("mongoose");
const { isEmail } = require("validator");
const bcrypt = require("bcrypt");
const { UserActions, WarehouseScopes, InventoryScopes, AllUIModules } = require("./../config/constants");
const schema = new mongoose.Schema(
{
@@ -36,22 +37,59 @@ const schema = new mongoose.Schema(
passwordResetToken: {
type: String,
},
image_url: {
type: String,
trim: true,
},
roles: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "UserRole",
},
],
permissions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "UserPermission",
},
],
permissions: {
inventoryScopes: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
refPath: "type",
},
type: {
type: String,
enum: InventoryScopes,
},
},
],
warehouseScopes: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
refPath: "type",
},
type: {
type: String,
enum: WarehouseScopes,
},
},
],
allowedUIModules: [
{
type: String,
enum: AllUIModules,
},
],
actions: [
{
type: String,
required: true,
enum: UserActions,
},
],
},
createdBy: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
}
},
},
{
timestamps: true,

View File

@@ -46,6 +46,11 @@ const schema = new mongoose.Schema(
enum: UserActions,
},
],
status: {
type: Boolean,
default: true,
required: true,
},
},
{
timestamps: true,

View File

@@ -1,4 +1,5 @@
const mongoose = require("mongoose");
const { UserActions, WarehouseScopes, InventoryScopes, AllUIModules } = require("./../config/constants");
const schema = new mongoose.Schema(
{
@@ -7,12 +8,50 @@ const schema = new mongoose.Schema(
required: true,
trim: true,
},
permissions: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "UserPermission",
},
],
permissions: {
inventoryScopes: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
refPath: "type",
},
type: {
type: String,
enum: InventoryScopes,
},
},
],
warehouseScopes: [
{
id: {
type: mongoose.Schema.Types.ObjectId,
refPath: "type",
},
type: {
type: String,
enum: WarehouseScopes,
},
},
],
allowedUIModules: [
{
type: String,
enum: AllUIModules,
},
],
actions: [
{
type: String,
required: true,
enum: UserActions,
},
],
},
status: {
type: Boolean,
default: true,
required: true,
},
},
{
timestamps: true,

View File

@@ -16,6 +16,10 @@ const schema = new mongoose.Schema(
type: String,
trim: true,
},
image_url: {
type: String,
trim: true,
},
company_id: {
type: mongoose.Schema.Types.ObjectId,
ref: "Company",
@@ -26,6 +30,12 @@ const schema = new mongoose.Schema(
ref: "Zone",
},
],
preferredInventories: [
{
type: mongoose.Schema.Types.ObjectId,
ref: "Inventory",
},
],
},
{
timestamps: true,

View File

@@ -14,7 +14,7 @@ const schema = new mongoose.Schema(
inventory: {
type: mongoose.Schema.Types.ObjectId,
ref: "Inventory",
required: true
required: true,
},
},
{
@@ -22,6 +22,8 @@ const schema = new mongoose.Schema(
}
);
schema.index({ name: 1, parent: 1, inventory: 1 }, { unique: true });
const WidgetFamily = mongoose.model("WidgetFamily", schema);
module.exports = WidgetFamily;