566 lines
18 KiB
JavaScript
566 lines
18 KiB
JavaScript
const mongoose = require("mongoose");
|
|
const Item = require("../models/Item");
|
|
const WidgetFamily = require("../models/WidgetFamily");
|
|
const {
|
|
PickItemTransaction,
|
|
PutItemTransaction,
|
|
ReserveItemTransaction,
|
|
CheckInItemTransaction,
|
|
CheckOutItemTransaction,
|
|
ReportItemTransaction,
|
|
AdjustItemTransaction,
|
|
} = require("../models/ItemTransaction");
|
|
const { S3 } = require("./../config/aws");
|
|
const ItemAssociation = require("../models/ItemAssociation");
|
|
const Sublevel = require("../models/Sublevel");
|
|
const { ReportItemForTypes } = require("../config/constants");
|
|
const { filterItems, filterItemAssociations } = require("./utils/aggregation");
|
|
module.exports = {
|
|
/**
|
|
* Gets the Item data by `id`
|
|
*/
|
|
getItemByID: async (req, res, next) => {
|
|
const { id } = req.params;
|
|
|
|
if (!id) {
|
|
res.status(400).send({ success: false, error: "Missing id param" });
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const itemData = await Item.findById(id).populate({ path: "widgetFamily", populate: "inventory" });
|
|
if (!itemData) {
|
|
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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Create a Item
|
|
*/
|
|
createItem: async (req, res, next) => {
|
|
let widgetFamily;
|
|
if (req.body.widgetFamilyId && mongoose.isValidObjectId(req.body.widgetFamilyId)) {
|
|
widgetFamily = await WidgetFamily.findById(req.body.widgetFamilyId);
|
|
}
|
|
const item = {
|
|
commonName: req.body.commonName,
|
|
formalName: req.body.formalName,
|
|
description: req.body.description,
|
|
manufacturer: req.body.manufacturer,
|
|
size: req.body.size,
|
|
color: req.body.color,
|
|
type: req.body.type,
|
|
unitOfMaterial: req.body.unitOfMaterial,
|
|
unitCost: req.body.unitCost,
|
|
packageCount: req.body.packageCount,
|
|
countPerPallet: req.body.countPerPallet,
|
|
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;
|
|
}
|
|
}
|
|
|
|
try {
|
|
const itemData = new Item(item);
|
|
|
|
await itemData.save();
|
|
if (!itemData) {
|
|
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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Update a Item detail
|
|
*/
|
|
updateItemByID: async (req, res, next) => {
|
|
const { id } = req.params;
|
|
let widgetFamily;
|
|
if (req.body.widgetFamilyId && mongoose.isValidObjectId(req.body.widgetFamilyId)) {
|
|
widgetFamily = await WidgetFamily.findById(req.body.widgetFamilyId);
|
|
}
|
|
|
|
if (!id) {
|
|
res.status(400).send("Missing id param");
|
|
return;
|
|
}
|
|
|
|
const item = {
|
|
commonName: req.body.commonName,
|
|
formalName: req.body.formalName,
|
|
description: req.body.description,
|
|
manufacturer: req.body.manufacturer,
|
|
size: req.body.size,
|
|
color: req.body.color,
|
|
type: req.body.type,
|
|
unitOfMaterial: req.body.unitOfMaterial,
|
|
unitCost: req.body.unitCost,
|
|
packageCount: req.body.packageCount,
|
|
countPerPallet: req.body.countPerPallet,
|
|
countPerPalletPackage: req.body.countPerPalletPackage,
|
|
customAttributes: req.body.customAttributes,
|
|
widgetFamily: widgetFamily,
|
|
};
|
|
|
|
try {
|
|
const itemData = await Item.findById(id);
|
|
if (!itemData) {
|
|
res.status(404);
|
|
return;
|
|
}
|
|
|
|
for (const key of Object.keys(item)) {
|
|
if (item[key] !== undefined) {
|
|
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);
|
|
}
|
|
},
|
|
|
|
/**
|
|
* Gets the Items data by filter
|
|
*/
|
|
getItemsByFilter: 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 filterItems(inventory, family, page, perPage);
|
|
|
|
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);
|
|
|
|
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);
|
|
}
|
|
},
|
|
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, usageReason, job } = 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);
|
|
let itemAssociation = await ItemAssociation.findOne({ item_id: item._id, sub_level_id: subLevelObj._id });
|
|
if (!itemAssociation) {
|
|
itemAssociation = await ItemAssociation.create({ item_id: item._id, sub_level_id: subLevelObj._id });
|
|
}
|
|
itemAssociation.totalQuantity = itemAssociation.totalQuantity + putQuantity;
|
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity + putQuantity;
|
|
await itemAssociation.save();
|
|
|
|
const itemTransaction = await PutItemTransaction.create({
|
|
type: "PUT",
|
|
performedOn: item,
|
|
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 } });
|
|
} 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, usageReason, job } = 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);
|
|
let itemAssociation = await ItemAssociation.findOne({ item_id: item._id, sub_level_id: subLevelObj._id });
|
|
if (!itemAssociation) {
|
|
itemAssociation = await ItemAssociation.create({ item_id: item._id, sub_level_id: subLevelObj._id });
|
|
}
|
|
itemAssociation.totalQuantity = itemAssociation.totalQuantity - pickupQuantity;
|
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity - pickupQuantity;
|
|
await itemAssociation.save();
|
|
|
|
const itemTransaction = await PickItemTransaction.create({
|
|
type: "PICK",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
pickupQuantity: pickupQuantity,
|
|
subLevel: subLevelObj,
|
|
usageReason: usageReason ? usageReason : "",
|
|
job: job,
|
|
});
|
|
res.send({ success: true, data: { itemAssociation, itemTransaction } });
|
|
} 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, 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({ success: false, error: "Quantity unavailable" });
|
|
return;
|
|
}
|
|
itemAssociation.reservedQuantity = itemAssociation.reservedQuantity + reserveQuantity;
|
|
itemAssociation.availableQuantity = itemAssociation.availableQuantity - reserveQuantity;
|
|
await itemAssociation.save();
|
|
|
|
const itemTransaction = await ReserveItemTransaction.create({
|
|
type: "RESERVE",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
reserveQuantity: reserveQuantity,
|
|
job: job,
|
|
pickupDate: pickupDate ? Date.parse(pickupDate) : undefined,
|
|
usageReason: usageReason ? usageReason : "",
|
|
});
|
|
res.send({ success: true, data: { itemAssociation, itemTransaction } });
|
|
} 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, usageReason, job } = req.body;
|
|
|
|
const itemTransaction = await CheckInItemTransaction.create({
|
|
type: "CHECK-IN",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
checkInMeterReading: checkInMeterReading,
|
|
hasIssue: hasIssue || false,
|
|
issueDescription: hasIssue ? issueDescription : "",
|
|
usageReason: usageReason ? usageReason : "",
|
|
job: job,
|
|
});
|
|
res.send({ success: true, data: { itemTransaction } });
|
|
} 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, usageReason, job } = req.body;
|
|
|
|
const itemTransaction = await CheckOutItemTransaction.create({
|
|
type: "CHECK-OUT",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
checkOutMeterReading: checkOutMeterReading,
|
|
usageReason: usageReason ? usageReason : "",
|
|
job: job,
|
|
});
|
|
res.send({ success: true, data: { itemTransaction } });
|
|
} 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, usageReason, job } = req.body;
|
|
if (!(reportingFor && ReportItemForTypes.includes(reportingFor))) {
|
|
res.status(400).send("Invalid value for checkOutMeterReading/job");
|
|
return;
|
|
}
|
|
|
|
const itemTransaction = await ReportItemTransaction.create({
|
|
type: "REPORT",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
reportingFor: reportingFor,
|
|
details: details ? details : "",
|
|
usageReason: usageReason ? usageReason : "",
|
|
job: job,
|
|
});
|
|
res.send({ success: true, data: { itemTransaction } });
|
|
} 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, usageReason, job } = 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();
|
|
|
|
const itemTransaction = await AdjustItemTransaction.create({
|
|
type: "ADJUST",
|
|
performedOn: item,
|
|
performedBy: res.locals.user,
|
|
lastRecordedQuantity,
|
|
recountedQuantity,
|
|
varianceRecordedInQuantity,
|
|
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 });
|
|
},
|
|
};
|