Merge pull request #14 from kfnawaz/feat/transaction-logs
Feat: Item Transaction & Inventory Policies
This commit is contained in:
@@ -59,6 +59,17 @@ const AUTHORIZATION_FAILURE_ERROR_MESSAGE =
|
|||||||
|
|
||||||
const SubLevelTypes = ["POSITION", "BIN", "PALLET"];
|
const SubLevelTypes = ["POSITION", "BIN", "PALLET"];
|
||||||
|
|
||||||
|
const ItemTransactionTypes = [
|
||||||
|
"PUT",
|
||||||
|
"PICK",
|
||||||
|
"RESERVE",
|
||||||
|
"CHECK-IN",
|
||||||
|
"CHECK-OUT",
|
||||||
|
"RESERVE"
|
||||||
|
];
|
||||||
|
|
||||||
|
const ReportItemForTypes = ["LOCATION", "ISSUE", "INCIDENT"];
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
UserActions,
|
UserActions,
|
||||||
InventoryScopes,
|
InventoryScopes,
|
||||||
@@ -75,4 +86,6 @@ module.exports = {
|
|||||||
AREA_ADMIN_ROLE: "area-admin",
|
AREA_ADMIN_ROLE: "area-admin",
|
||||||
AUTHENTICATION_FAILURE_ERROR_MESSAGE,
|
AUTHENTICATION_FAILURE_ERROR_MESSAGE,
|
||||||
AUTHORIZATION_FAILURE_ERROR_MESSAGE,
|
AUTHORIZATION_FAILURE_ERROR_MESSAGE,
|
||||||
|
ItemTransactionTypes,
|
||||||
|
ReportItemForTypes,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -163,14 +163,34 @@ const createSublevels = async (subLevels, level, parent = undefined, depth = 0)
|
|||||||
return sub_levels_list;
|
return sub_levels_list;
|
||||||
};
|
};
|
||||||
|
|
||||||
const createInventory = async ({ name, type }) => {
|
const createInventory = async ({ name, type, policies }) => {
|
||||||
if (!(name && type)) {
|
if (!(name && type)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const preferredLocations = [];
|
||||||
|
if (policies.preferredLocations && Array.isArray(policies.preferredLocations)) {
|
||||||
|
for (const preferredLocation of policies.preferredLocations) {
|
||||||
|
preferredLocations.push({ id: preferredLocation.id, type: preferredLocation.type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
return await Inventory.create({
|
return await Inventory.create({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
policies: verifiedPolicies,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -29,17 +29,36 @@ module.exports = {
|
|||||||
* Create a Inventory
|
* Create a Inventory
|
||||||
*/
|
*/
|
||||||
createInventory: async (req, res, next) => {
|
createInventory: async (req, res, next) => {
|
||||||
const { name, type } = req.body;
|
const { name, type, policies } = req.body;
|
||||||
|
|
||||||
if (!(name && type)) {
|
if (!(name && type)) {
|
||||||
res.status(400).send("Missing params param");
|
res.status(400).send("Missing params param");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
const preferredLocations = [];
|
||||||
|
if (policies.preferredLocations && Array.isArray(policies.preferredLocations)) {
|
||||||
|
for (const preferredLocation of policies.preferredLocations) {
|
||||||
|
preferredLocations.push({ id: preferredLocation.id, type: preferredLocation.type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const inventoryData = new Inventory({
|
const inventoryData = new Inventory({
|
||||||
name,
|
name,
|
||||||
type,
|
type,
|
||||||
|
policies: verifiedPolicies,
|
||||||
});
|
});
|
||||||
|
|
||||||
await inventoryData.save();
|
await inventoryData.save();
|
||||||
@@ -64,7 +83,7 @@ module.exports = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { name, type } = req.body;
|
const { name, type, policies } = req.body;
|
||||||
|
|
||||||
if (!(name || type)) {
|
if (!(name || type)) {
|
||||||
res.status(400).send("Missing data in body");
|
res.status(400).send("Missing data in body");
|
||||||
@@ -81,6 +100,37 @@ module.exports = {
|
|||||||
if (name) inventoryData.name = name;
|
if (name) inventoryData.name = name;
|
||||||
if (type) inventoryData.type = type;
|
if (type) inventoryData.type = type;
|
||||||
|
|
||||||
|
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
|
||||||
|
? policies.alerting.lowestStockLevel
|
||||||
|
: inventoryData.policies.alerting.lowestStockLevel,
|
||||||
|
highestStockLevel:
|
||||||
|
policies.alerting && policies.alerting.highestStockLevel
|
||||||
|
? policies.alerting.highestStockLevel
|
||||||
|
: inventoryData.policies.alerting.highestStockLevel,
|
||||||
|
alertStockLevel:
|
||||||
|
policies.alerting && policies.alerting.alertStockLevel
|
||||||
|
? policies.alerting.alertStockLevel
|
||||||
|
: inventoryData.policies.alerting.alertStockLevel,
|
||||||
|
reOrderLevel:
|
||||||
|
policies.alerting && policies.alerting.reOrderLevel ? policies.alerting.reOrderLevel : inventoryData.policies.alerting.reOrderLevel,
|
||||||
|
},
|
||||||
|
replenishment: policies.replenishment || inventoryData.policies.replenishment,
|
||||||
|
preferredLocations: preferredLocations,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
await inventoryData.save();
|
await inventoryData.save();
|
||||||
res.send({ success: true, data: inventoryData });
|
res.send({ success: true, data: inventoryData });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
@@ -2,7 +2,19 @@ const mongoose = require("mongoose");
|
|||||||
const Item = require("../models/Item");
|
const Item = require("../models/Item");
|
||||||
const WidgetFamily = require("../models/WidgetFamily");
|
const WidgetFamily = require("../models/WidgetFamily");
|
||||||
const Inventory = require("../models/Inventory");
|
const Inventory = require("../models/Inventory");
|
||||||
const { InventoryTypes } = require("../config/constants");
|
const {
|
||||||
|
PickItemTransaction,
|
||||||
|
PutItemTransaction,
|
||||||
|
ReserveItemTransaction,
|
||||||
|
CheckInItemTransaction,
|
||||||
|
CheckOutItemTransaction,
|
||||||
|
ReportItemTransaction,
|
||||||
|
AdjustItemTransaction,
|
||||||
|
} = require("../models/ItemTransaction");
|
||||||
|
|
||||||
|
const ItemAssociation = require("../models/ItemAssociation");
|
||||||
|
const Sublevel = require("../models/Sublevel");
|
||||||
|
const { InventoryTypes, ReportItemForTypes } = require("../config/constants");
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
/**
|
/**
|
||||||
@@ -194,4 +206,260 @@ module.exports = {
|
|||||||
next(error);
|
next(error);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
putItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { putQuantity, subLevel } = req.body;
|
||||||
|
if (!(putQuantity && putQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
|
||||||
|
res.status(400).send("Invalid value for putQuantity/subLevel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subLevelObj = await Sublevel.findById(subLevel);
|
||||||
|
const itemAssociation = await ItemAssociation.findOne({ item_id: item._id, sub_level_id: subLevelObj._id });
|
||||||
|
itemAssociation.totalQuantity = itemAssociation.totalQuantity + putQuantity;
|
||||||
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity + putQuantity;
|
||||||
|
await itemAssociation.save();
|
||||||
|
|
||||||
|
await PutItemTransaction.create({
|
||||||
|
type: "PUT",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
putQuantity: putQuantity,
|
||||||
|
subLevel: subLevelObj,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
pickItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pickupQuantity, subLevel } = req.body;
|
||||||
|
if (!(pickupQuantity && pickupQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
|
||||||
|
res.status(400).send("Invalid value for pickupQuantity/subLevel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subLevelObj = await Sublevel.findById(subLevel);
|
||||||
|
const itemAssociation = await ItemAssociation.findOne({ item_id: item._id, sub_level_id: subLevelObj._id });
|
||||||
|
itemAssociation.totalQuantity = itemAssociation.totalQuantity - pickupQuantity;
|
||||||
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity - pickupQuantity;
|
||||||
|
await itemAssociation.save();
|
||||||
|
|
||||||
|
await PickItemTransaction.create({
|
||||||
|
type: "PICK",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
pickupQuantity: pickupQuantity,
|
||||||
|
subLevel: subLevelObj,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reserveItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
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");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const itemAssociation = await ItemAssociation.findOne({ item_id: item._id, availableQuantity: { $gte: reserveQuantity } });
|
||||||
|
itemAssociation.reservedQuantity = itemAssociation.reservedQuantity + reserveQuantity;
|
||||||
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity - reserveQuantity;
|
||||||
|
await itemAssociation.save();
|
||||||
|
|
||||||
|
await ReserveItemTransaction.create({
|
||||||
|
type: "RESERVE",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
reserveQuantity: reserveQuantity,
|
||||||
|
job: job,
|
||||||
|
pickupDate: Date.parse(pickupDate),
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkInItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { checkInMeterReading, hasIssue, issueDescription } = req.body;
|
||||||
|
if (!(checkInMeterReading && checkInMeterReading > 0)) {
|
||||||
|
res.status(400).send("Invalid value for checkInMeterReading");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CheckInItemTransaction.create({
|
||||||
|
type: "CHECK-IN",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
checkInMeterReading: checkInMeterReading,
|
||||||
|
hasIssue: hasIssue,
|
||||||
|
issueDescription: hasIssue ? issueDescription : "",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkOutItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
await CheckOutItemTransaction.create({
|
||||||
|
type: "CHECK-OUT",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
checkOutMeterReading: checkOutMeterReading,
|
||||||
|
job: job,
|
||||||
|
usageReason: usageReason ? usageReason : "",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
reportItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { reportingFor, details } = req.body;
|
||||||
|
if (!(reportingFor && ReportItemForTypes.includes(reportingFor))) {
|
||||||
|
res.status(400).send("Invalid value for checkOutMeterReading/job");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await ReportItemTransaction.create({
|
||||||
|
type: "REPORT",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
reportingFor: reportingFor,
|
||||||
|
details: details ? details : "",
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
adjustItem: async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const { id } = req.params;
|
||||||
|
if (!id || mongoose.isValidObjectId(id)) {
|
||||||
|
res.status(400).send("Missing/Invalid id param");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const item = await Item.findById(id);
|
||||||
|
if (!item) {
|
||||||
|
res.status(404).send("item not found");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { recountedQuantity, damagedQuantity, subLevel } = req.body;
|
||||||
|
if (!(recountedQuantity && recountedQuantity > 0) || !(subLevel && mongoose.isValidObjectId(subLevel))) {
|
||||||
|
res.status(400).send("Invalid value for pickupQuantity/subLevel");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subLevelObj = await Sublevel.findById(subLevel);
|
||||||
|
const itemAssociation = await ItemAssociation.findOne({ item_id: item._id, sub_level_id: subLevelObj._id });
|
||||||
|
const lastRecordedQuantity = itemAssociation.totalQuantity;
|
||||||
|
const varianceRecordedInQuantity = itemAssociation.totalQuantity - recountedQuantity;
|
||||||
|
const totalAdjustment = varianceRecordedInQuantity + damagedQuantity;
|
||||||
|
itemAssociation.totalQuantity = itemAssociation.totalQuantity - totalAdjustment;
|
||||||
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity - totalAdjustment;
|
||||||
|
await itemAssociation.save();
|
||||||
|
|
||||||
|
await AdjustItemTransaction.create({
|
||||||
|
type: "ADJUST",
|
||||||
|
performedOn: item,
|
||||||
|
performedBy: res.locals.user,
|
||||||
|
lastRecordedQuantity,
|
||||||
|
recountedQuantity,
|
||||||
|
varianceRecordedInQuantity,
|
||||||
|
damagedQuantity,
|
||||||
|
totalAdjustment,
|
||||||
|
newAdjustedQuantity: itemAssociation.totalQuantity,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
next(error);
|
||||||
|
}
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
const router = require("express").Router();
|
const router = require("express").Router();
|
||||||
const controller = require("./item.controller");
|
const controller = require("./item.controller");
|
||||||
|
const { AuthenticateMiddleware, ItemTransactionCheck } = require("./utils/authorize");
|
||||||
/**
|
/**
|
||||||
* @route /item/
|
* @route /item/
|
||||||
*/
|
*/
|
||||||
@@ -21,4 +21,39 @@ router.get("/filter", controller.getItemsByFilter);
|
|||||||
*/
|
*/
|
||||||
router.get("/:id", controller.getItemByID);
|
router.get("/:id", controller.getItemByID);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/pick
|
||||||
|
*/
|
||||||
|
router.post("/:id/pick", AuthenticateMiddleware, ItemTransactionCheck, controller.pickItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/put
|
||||||
|
*/
|
||||||
|
router.post("/:id/put", AuthenticateMiddleware, ItemTransactionCheck, controller.putItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/reserve
|
||||||
|
*/
|
||||||
|
router.post("/:id/reserve", AuthenticateMiddleware, ItemTransactionCheck, controller.reserveItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/check-in
|
||||||
|
*/
|
||||||
|
router.post("/:id/check-in", AuthenticateMiddleware, ItemTransactionCheck, controller.checkInItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/check-out
|
||||||
|
*/
|
||||||
|
router.post("/:id/check-out", AuthenticateMiddleware, ItemTransactionCheck, controller.checkOutItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/report
|
||||||
|
*/
|
||||||
|
router.post("/:id/report", AuthenticateMiddleware, ItemTransactionCheck, controller.reportItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @route /item/:id/adjust
|
||||||
|
*/
|
||||||
|
router.post("/:id/adjust", AuthenticateMiddleware, ItemTransactionCheck, controller.adjustItem);
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
|||||||
@@ -1,38 +1,22 @@
|
|||||||
const jwt = require("jsonwebtoken");
|
const jwt = require("jsonwebtoken");
|
||||||
const User = require("../../models/User");
|
const User = require("../../models/User");
|
||||||
const UserRole = require("../../models/UserRole");
|
const UserRole = require("../../models/UserRole");
|
||||||
const {
|
const { SUPER_ADMIN_ROLE, AUTHORIZATION_FAILURE_ERROR_MESSAGE } = require("../../config/constants");
|
||||||
SUPER_ADMIN_ROLE,
|
|
||||||
AUTHORIZATION_FAILURE_ERROR_MESSAGE,
|
|
||||||
} = require("../../config/constants");
|
|
||||||
const { JWT_SECRET } = require("../../config/env");
|
const { JWT_SECRET } = require("../../config/env");
|
||||||
const constants = require("../../config/constants");
|
const constants = require("../../config/constants");
|
||||||
|
|
||||||
const authenticate = async (token) => {
|
const authenticate = async (token) => {
|
||||||
const decodedToken = jwt.verify(token, JWT_SECRET);
|
const decodedToken = jwt.verify(token, JWT_SECRET);
|
||||||
if (decodedToken) {
|
if (decodedToken) {
|
||||||
return await User.findById(decodedToken.id)
|
return await User.findById(decodedToken.id).populate({ path: "roles", populate: "permissions" }).populate("permissions");
|
||||||
.populate({ path: "roles", populate: "permissions" })
|
|
||||||
.populate("permissions");
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const authorize = async (
|
const authorize = async (user, requiredRoles = [], requiredPermissions = []) => {
|
||||||
user,
|
|
||||||
requiredRoles = [],
|
|
||||||
requiredPermissions = []
|
|
||||||
) => {
|
|
||||||
const userRoles = user.roles.map((_) => _._id);
|
const userRoles = user.roles.map((_) => _._id);
|
||||||
const userPermissions = [
|
const userPermissions = [...user.permissions.map((_) => _._id), ...userRoles.map((_) => _.permissions).flat()];
|
||||||
...user.permissions.map((_) => _._id),
|
|
||||||
...userRoles.map((_) => _.permissions).flat(),
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
return user != undefined && requiredRoles.every((_) => userRoles.includes(_)) && requiredPermissions.every((_) => userPermissions.includes(_));
|
||||||
user != undefined &&
|
|
||||||
requiredRoles.every((_) => userRoles.includes(_)) &&
|
|
||||||
requiredPermissions.every((_) => userPermissions.includes(_))
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
@@ -41,11 +25,13 @@ module.exports = {
|
|||||||
if (authorize(res.locals.user, [SuperAdmin.id])) {
|
if (authorize(res.locals.user, [SuperAdmin.id])) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
res
|
res.status(403).send({ success: false, error: AUTHORIZATION_FAILURE_ERROR_MESSAGE });
|
||||||
.status(403)
|
|
||||||
.send({ success: false, error: AUTHORIZATION_FAILURE_ERROR_MESSAGE });
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
ItemTransactionCheck: async (req, res, next) => {
|
||||||
|
// WIP
|
||||||
|
next();
|
||||||
|
},
|
||||||
AuthenticateMiddleware: async (req, res, next) => {
|
AuthenticateMiddleware: async (req, res, next) => {
|
||||||
try {
|
try {
|
||||||
const token = req.headers.authorization || "";
|
const token = req.headers.authorization || "";
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
const mongoose = require("mongoose");
|
const mongoose = require("mongoose");
|
||||||
const { InventoryTypes } = require("./../config/constants");
|
const { InventoryTypes, WarehouseScopes } = require("./../config/constants");
|
||||||
|
|
||||||
const schema = new mongoose.Schema(
|
const schema = new mongoose.Schema(
|
||||||
{
|
{
|
||||||
@@ -15,15 +15,42 @@ const schema = new mongoose.Schema(
|
|||||||
enum: InventoryTypes,
|
enum: InventoryTypes,
|
||||||
},
|
},
|
||||||
policies: {
|
policies: {
|
||||||
tracking: {
|
orderTracking: {
|
||||||
type: Object, // Create a different model and reference it here once more details available
|
type: Object, // Create a different model and reference it here once more details available
|
||||||
},
|
},
|
||||||
alerting: {
|
alerting: {
|
||||||
type: Object, // Create a different model and reference it here once more details available
|
lowestStockLevel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
highestStockLevel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
alertStockLevel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
reOrderLevel: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
replenishment: {
|
replenishment: {
|
||||||
type: Object, // Create a different model and reference it here once more details available
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,15 @@ const schema = new mongoose.Schema(
|
|||||||
type: mongoose.Schema.Types.ObjectId,
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
ref: "Sublevel",
|
ref: "Sublevel",
|
||||||
},
|
},
|
||||||
quantity: {
|
totalQuantity: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
reservedQuantity: {
|
||||||
|
type: Number,
|
||||||
|
default: 0,
|
||||||
|
},
|
||||||
|
availableQuantity: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
|||||||
165
src/models/ItemTransaction.js
Normal file
165
src/models/ItemTransaction.js
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
const mongoose = require("mongoose");
|
||||||
|
const { ItemTransactionTypes, ReportItemForTypes } = require("../config/constants");
|
||||||
|
|
||||||
|
const schema = new mongoose.Schema(
|
||||||
|
{
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
trim: true,
|
||||||
|
enum: ItemTransactionTypes,
|
||||||
|
},
|
||||||
|
performedOn: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "Item",
|
||||||
|
},
|
||||||
|
performedBy: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "User",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: true,
|
||||||
|
discriminatorKey: "kind",
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const ItemTransaction = mongoose.model("ItemTransaction", schema);
|
||||||
|
|
||||||
|
const PutItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"Put",
|
||||||
|
new mongoose.Schema({
|
||||||
|
putQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
subLevel: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "Sublevel"
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const PickItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"Pick",
|
||||||
|
new mongoose.Schema({
|
||||||
|
pickupQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
subLevel: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
ref: "Sublevel",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const ReserveItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"Reserve",
|
||||||
|
new mongoose.Schema({
|
||||||
|
reserveQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
pickupDate: {
|
||||||
|
type: Date,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const CheckInItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"CheckIn",
|
||||||
|
new mongoose.Schema({
|
||||||
|
checkInMeterReading: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
hasIssue: {
|
||||||
|
type: Boolean,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
issueDescription: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const CheckOutItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"CheckOut",
|
||||||
|
new mongoose.Schema({
|
||||||
|
checkOutMeterReading: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
job: {
|
||||||
|
type: mongoose.Schema.Types.ObjectId,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
usageReason: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const ReportItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"Report",
|
||||||
|
new mongoose.Schema({
|
||||||
|
reportingFor: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
enum: ReportItemForTypes,
|
||||||
|
},
|
||||||
|
details: {
|
||||||
|
type: String,
|
||||||
|
trim: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const AdjustItemTransaction = ItemTransaction.discriminator(
|
||||||
|
"Adjust",
|
||||||
|
new mongoose.Schema({
|
||||||
|
lastRecordedQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
recountedQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
varianceRecordedInQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
damagedQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
totalAdjustment: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
newAdjustedQuantity: {
|
||||||
|
type: Number,
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
ItemTransaction,
|
||||||
|
PutItemTransaction,
|
||||||
|
PickItemTransaction,
|
||||||
|
ReserveItemTransaction,
|
||||||
|
CheckInItemTransaction,
|
||||||
|
CheckOutItemTransaction,
|
||||||
|
ReportItemTransaction,
|
||||||
|
AdjustItemTransaction,
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user