From 59b38747096d06408efee58c2fd53050f44350eb Mon Sep 17 00:00:00 2001 From: Dave Luhman Date: Fri, 17 May 2024 21:12:56 -0500 Subject: [PATCH] Refactor tool management and search functionality Refactored code in `tool.js` to enhance readability, maintainability, and efficiency. Standardized functions for checking in and out tools, searching tools, creating and updating tool entries, and archiving tools. Improved error handling and validation across the board to ensure robustness. Default values for `serviceAssignment` and `category` in `Tool.model.js` were set to prevent issues with tool creation where these fields might be undefined, improving data integrity and user experience when interacting with the tool management system. Additionally, introduced a bulk update mechanism and optimized tool lookup to reduce load times and improve efficiency when processing large sets of tools. These changes aim to streamline the tool management process, making it more intuitive and less error-prone for users. - Set default values for `serviceAssignment` and `category` in `Tool` model to improve tool creation UX. - Cleaned up and documented middleware functions for better maintainability. - Implemented better error handling and validation for tool operations to enhance system robustness. These improvements directly address usability concerns and potential data inconsistencies, markedly enhancing the application's overall functionality and user satisfaction. --- src/middleware/tool.js | 715 ++++++++++++++++++++------------------- src/models/Tool.model.js | 6 +- 2 files changed, 364 insertions(+), 357 deletions(-) diff --git a/src/middleware/tool.js b/src/middleware/tool.js index d2ab858c..2588cdd9 100644 --- a/src/middleware/tool.js +++ b/src/middleware/tool.js @@ -83,390 +83,395 @@ async function getCheckedInTools() { if (activeServiceAssignmentArray[ii] == tools[i].serviceAssignment?._id) { checkedInTools.push(tools[i]) } - }} - return checkedInTools - } - async function getCheckedOutTools() { - const tools = await Tool.find().where('archived').equals(false) - const activeServiceAssignmentsDocs = await ServiceAssignment.find().where('type').ne('Stockroom') - const activeServiceAssignmentArray = activeServiceAssignmentsDocs.map(item => { return item._id.valueOf() }) - const checkedInTools = [] - for (let i = 0; i < tools.length; i++) { - for (let ii = 0; ii < activeServiceAssignmentArray.length; ii++) { - if (activeServiceAssignmentArray[ii] == tools[i].serviceAssignment?._id) { - checkedInTools.push(tools[i]) - } - }} - return checkedInTools } - /** - * - * @param {string} req.body.searchBy The key to search by - * @param {string} req.body.searchValue The terms to search for - * @param {number} req.query.p Page Number - * @param {object} res.locals.pagination {page: targetPage, pageCount: pageCount} pagination data - * @param {array} res.locals.tools returns array of tools in response - * @param {*} next - * @returns {array} - */ - async function searchTools(req, res, next) { - console.info('[MW] searchTools-in'.bgBlue.white) - const { sortField, sortOrder } = req.user.preferences - const { searchBy, searchTerm } = req.body - let tools - switch (searchBy) { - case 'Search By': - res.locals.message = 'You must specify search parameters' - return next() - case 'serviceAssignment': - res.locals.searchBy = searchBy - res.locals.searchTerm = searchTerm - res.locals.tools = await Tool.where('serviceAssignment') - .equals(searchTerm) - .sort({ [sortField]: sortOrder }) - .exec() - break - case 'category': - res.locals.searchBy = searchBy - res.locals.searchTerm = searchTerm - res.locals.tools = await Tool.where('category') - .equals(searchTerm) - .sort({ [sortField]: sortOrder }) - .exec() - break - case 'status': - res.locals.searchBy = searchBy - res.locals.searchTerm = searchTerm - if(searchTerm === 'Checked In') res.locals.tools = await getCheckedInTools() - else res.locals.tools = await getCheckedOutTools() - break - default: - res.locals.searchTerm = searchTerm - res.locals.searchBy = searchBy - res.locals.tools = await Tool.find({ - [searchBy]: { $eq: searchTerm } - }).sort({ [sortField]: sortOrder }) - break + } + return checkedInTools +} +async function getCheckedOutTools() { + const tools = await Tool.find().where('archived').equals(false) + const activeServiceAssignmentsDocs = await ServiceAssignment.find().where('type').ne('Stockroom') + const activeServiceAssignmentArray = activeServiceAssignmentsDocs.map(item => { return item._id.valueOf() }) + const checkedInTools = [] + for (let i = 0; i < tools.length; i++) { + for (let ii = 0; ii < activeServiceAssignmentArray.length; ii++) { + if (activeServiceAssignmentArray[ii] == tools[i].serviceAssignment?._id) { + checkedInTools.push(tools[i]) + } } - res.locals.totalFound = res.locals.tools.length - console.info('[MW] searchTools-out'.bgWhite.blue) - return next() } + return checkedInTools +} +/** + * + * @param {string} req.body.searchBy The key to search by + * @param {string} req.body.searchValue The terms to search for + * @param {number} req.query.p Page Number + * @param {object} res.locals.pagination {page: targetPage, pageCount: pageCount} pagination data + * @param {array} res.locals.tools returns array of tools in response + * @param {*} next + * @returns {array} + */ +async function searchTools(req, res, next) { + console.info('[MW] searchTools-in'.bgBlue.white) + const { sortField, sortOrder } = req.user.preferences + const { searchBy, searchTerm } = req.body + let tools + switch (searchBy) { + case 'Search By': + res.locals.message = 'You must specify search parameters' + return next() + case 'serviceAssignment': + res.locals.searchBy = searchBy + res.locals.searchTerm = searchTerm + res.locals.tools = await Tool.where('serviceAssignment') + .equals(searchTerm) + .sort({ [sortField]: sortOrder }) + .exec() + break + case 'category': + res.locals.searchBy = searchBy + res.locals.searchTerm = searchTerm + res.locals.tools = await Tool.where('category') + .equals(searchTerm) + .sort({ [sortField]: sortOrder }) + .exec() + break + case 'status': + res.locals.searchBy = searchBy + res.locals.searchTerm = searchTerm + if (searchTerm === 'Checked In') res.locals.tools = await getCheckedInTools() + else res.locals.tools = await getCheckedOutTools() + break + default: + res.locals.searchTerm = searchTerm + res.locals.searchBy = searchBy + res.locals.tools = await Tool.find({ + [searchBy]: { $eq: searchTerm } + }).sort({ [sortField]: sortOrder }) + break + } + res.locals.totalFound = res.locals.tools.length + console.info('[MW] searchTools-out'.bgWhite.blue) + return next() +} - /** - * - * @param {object} req.body The tool object to create - * @param {string} res.locals.message The message to display to the user - * @param {*} next - * @returns - */ - async function createTool(req, res, next) { - try { - console.info('[MW] createTool-in'.bgBlue.white) - const { - serialNumber, - modelNumber, - barcode, - description, - toolID, - serviceAssignment, - category, - manufacturer, - width, +/** + * + * @param {object} req.body The tool object to create + * @param {string} res.locals.message The message to display to the user + * @param {*} next + * @returns + */ +async function createTool(req, res, next) { + try { + console.info('[MW] createTool-in'.bgBlue.white) + const { + serialNumber, + modelNumber, + barcode, + description, + toolID, + serviceAssignment, + category, + manufacturer, + width, + height, + length, + weight + } = req.body + if (!(serialNumber || modelNumber) || !barcode) { + throw new Error({ message: 'Missing required fields', status: 400 }) + } + const existing = await Tool.findOne({ + $or: [{ serialNumber }, { barcode }] + }) + if (existing) { + res.locals.tools = mutateToArray(existing) + throw new Error({ message: 'Tool already exists', status: 400 }) + } + // TODO: verify input data is sanitized + const newTool = await Tool.create({ + serialNumber, + modelNumber, + barcode, + description, + toolID, + serviceAssignment, + category, + manufacturer, + size: { height, + width, length, weight - } = req.body - if (!(serialNumber || modelNumber) || !barcode) { - throw new Error({ message: 'Missing required fields', status: 400 }) - } - const existing = await Tool.findOne({ - $or: [{ serialNumber }, { barcode }] - }) - if (existing) { - res.locals.tools = mutateToArray(existing) - throw new Error({ message: 'Tool already exists', status: 400 }) - } - // TODO: verify input data is sanitized - const newTool = await Tool.create({ - serialNumber, + }, + updatedBy: req.user._id, + createdBy: req.user._id + }) + if (!newTool) { + throw new Error({ message: 'Could not create tool', status: 500 }) + } + await ToolHistory.create({ + _id: newTool._id, + history: [newTool] + }) + res.locals.message = 'Successfully Made A New Tool' + res.locals.tools = [newTool] + res.locals.pagination = { pageCount: 1 } + res.status(201) + console.info(`[MW] Tool Successfully Created ${newTool._id}`.green) + console.info('[MW] createTool-out-3'.bgWhite.blue) + next() + } catch (error) { + res.locals.message = error.message + res.status(error.status || 500).redirect('back') + } +} + +async function updateToolHistory(toolID) { + const oldTool = await Tool.findById(toolID) + await ToolHistory.findByIdAndUpdate( + { _id: toolID }, + { + $push: { history: oldTool }, + $inc: { __v: 1 }, + $set: { updatedAt: Date.now() } + } + ) +} + +/** + * + * @param {*} req.body._id The id of the tool to update + * @param {*} res + * @param {*} next + */ +async function updateTool(req, res, next) { + console.info('[MW] updateTool-in'.bgBlue.white) + + if(!req.body.serviceAssignment || req.body.serviceAssignment == 'null' || req.body.serviceAssignment == undefined) { + req.body.serviceAssignment = '64a34b651288871770df1086' + } + if(!req.body.category || req.body.category == 'null' || req.body.category == undefined) { + req.body.category = '64a1c3d8d71e121dfd39b7ab' + } + // block level function to update a single tool + const { + id, + modelNumber, + description, + toolID, + serviceAssignment, + category, + manufacturer, + width, + height, + length, + weight + } = req.body + const updatedTool = await Tool.findByIdAndUpdate( + { $eq: id }, + { modelNumber, - barcode, description, toolID, - serviceAssignment, - category, + serviceAssignment: serviceAssignment, + category: category, manufacturer, size: { - height, width, + height, length, weight }, - updatedBy: req.user._id, - createdBy: req.user._id - }) - if (!newTool) { - throw new Error({ message: 'Could not create tool', status: 500 }) - } - await ToolHistory.create({ - _id: newTool._id, - history: [newTool] - }) - res.locals.message = 'Successfully Made A New Tool' - res.locals.tools = [newTool] - res.locals.pagination = { pageCount: 1 } - res.status(201) - console.info(`[MW] Tool Successfully Created ${newTool._id}`.green) - console.info('[MW] createTool-out-3'.bgWhite.blue) - next() - } catch (error) { - res.locals.message = error.message - res.status(error.status || 500).redirect('back') - } - } - - async function updateToolHistory(toolID) { - const oldTool = await Tool.findById(toolID) - await ToolHistory.findByIdAndUpdate( - { _id: toolID }, - { - $push: { history: oldTool }, $inc: { __v: 1 }, $set: { updatedAt: Date.now() } - } + }, + { new: true } ) - } - /** - * - * @param {*} req.body._id The id of the tool to update - * @param {*} res - * @param {*} next - */ - async function updateTool(req, res, next) { - console.info('[MW] updateTool-in'.bgBlue.white) - const ut = async (newToolData) => { - const { - id, - modelNumber, - description, - toolID, - serviceAssignment, - category, - manufacturer, - width, - height, - length, - weight - } = newToolData - updateToolHistory(id) - const updatedTool = await Tool.findByIdAndUpdate( - { $eq: id }, - { - modelNumber, - description, - toolID, - serviceAssignment, - category, - manufacturer, - size: { - width, - height, - length, - weight - }, - $inc: { __v: 1 }, - $set: { updatedAt: Date.now() } - }, - { new: true } - ) + const updatedToolArray = [] + // if (typeof req.body.id === 'string') { + console.table(req.body) + updateToolHistory(id) // Update the tools history + updatedToolArray.push(updatedTool) + // } + // if (Array.isArray(req.body._id) && req.body._id.length > 0) { + // for (let i = 0; i < req.body.id.length > 100; i++) { + // const updatedTool = await ut({ + // _id: req.body.id[i], + // modelNumber: req.body.modelNumber[i], + // description: req.body.description[i], + // toolID: req.body.toolID[i], + // serviceAssignment: req.body.serviceAssignment[i], + // category: req.body.category[i], + // manufacturer: req.body.manufacturer[i], + // size: { + // width: req.body.size.width[i], + // height: req.body.size.height[i], + // length: req.body.size.length[i], + // weight: req.body.size.weight[i] + // }, + // $inc: { __v: 1 }, + // $set: { updatedAt: Date.now() } + // }) + // updatedToolArray.push(updatedTool) + // } + // } + res.locals.tools = updatedToolArray + res.status(200) + console.info('[MW] Successfully Updated Tools: '.green + updatedToolArray) + console.info('[MW] updateTool-out-1'.bgWhite.blue) + next() +} +/** + * archiveTool - Archives a tool + * @param {string} req.params.id The id of the tool to archive + * @param {string} res.locals.message The message to display to the user + * @param {array} res.locals.tools The tool that was archived + * @param {number} res.status The status code to return + * @param {*} next + */ +async function archiveTool(req, res, next) { + console.info('[MW] archiveTool-in'.bgBlue.white) + const { id } = req.params + const archivedTool = await Tool.findByIdAndUpdate( + { _id: id }, + { archived: true }, + { new: true } + ) + await ToolHistory.findByIdAndUpdate( + { _id: id }, + { $push: { history: [archivedTool] } }, + { new: true } + ) + res.locals.message = 'Successfully Marked Tool Archived' + res.locals.tools = [archivedTool] + res.status(201) + console.info('[MW] archiveTool-out-1'.bgWhite.blue) + next() +} - return updatedTool - } - const updatedToolArray = [] - if (typeof req.body.id === 'string') { - console.table(req.body) - const updatedTool = await ut(req.body) - updatedToolArray.push(updatedTool) - } - if (Array.isArray(req.body._id) && req.body._id.length > 0) { - for (let i = 0; i < req.body.id.length > 100; i++) { - const updatedTool = await ut({ - _id: req.body.id[i], - modelNumber: req.body.modelNumber[i], - description: req.body.description[i], - toolID: req.body.toolID[i], - serviceAssignment: req.body.serviceAssignment[i], - category: req.body.category[i], - manufacturer: req.body.manufacturer[i], - size: { - width: req.body.size.width[i], - height: req.body.size.height[i], - length: req.body.size.length[i], - weight: req.body.size.weight[i] - }, - $inc: { __v: 1 }, - $set: { updatedAt: Date.now() } - }) - updatedToolArray.push(updatedTool) - } - } - res.locals.tools = updatedToolArray - res.locals.pagination = { page: 1, pageCount: 1 } - res.status(200) - console.info('[MW] Successfully Updated Tools: '.green + updatedToolArray) - console.info('[MW] updateTool-out-1'.bgWhite.blue) - next() +async function checkTools(req, res, next) { + if (!req.body.searchTerms) { + res.locals.message = 'No Tools Submitted For Status Change' + console.warn('[MW checkTools-out-1'.bgWhite.blue) + res.status(400).redirect('back') + return next() } - /** - * archiveTool - Archives a tool - * @param {string} req.params.id The id of the tool to archive - * @param {string} res.locals.message The message to display to the user - * @param {array} res.locals.tools The tool that was archived - * @param {number} res.status The status code to return - * @param {*} next - */ - async function archiveTool(req, res, next) { - console.info('[MW] archiveTool-in'.bgBlue.white) - const { id } = req.params - const archivedTool = await Tool.findByIdAndUpdate( - { _id: id }, - { archived: true }, - { new: true } - ) - await ToolHistory.findByIdAndUpdate( - { _id: id }, - { $push: { history: [archivedTool] } }, - { new: true } - ) - res.locals.message = 'Successfully Marked Tool Archived' - res.locals.tools = [archivedTool] - res.status(201) - console.info('[MW] archiveTool-out-1'.bgWhite.blue) - next() + const search = deduplicateArray(req.body.searchTerms.split(/\r?\n/)) + const toolsToBeChanged = await lookupToolWrapper(search) + if (toolsToBeChanged.length === 0) { + res.locals.message = 'No Tools Found Matching ' } - - async function checkTools(req, res, next) { - if (!req.body.searchTerms) { - res.locals.message = 'No Tools Submitted For Status Change' - console.warn('[MW checkTools-out-1'.bgWhite.blue) - res.status(400).redirect('back') - return next() - } - const search = deduplicateArray(req.body.searchTerms.split(/\r?\n/)) - const toolsToBeChanged = await lookupToolWrapper(search) - if (toolsToBeChanged.length === 0) { - res.locals.message = 'No Tools Found Matching ' - } - res.locals.target = (req.body.serviceAssignmentInput === '') ? req.body.serviceAssignmentSelector : req.body.serviceAssignmentInput - res.locals.tools = toolsToBeChanged - next() + res.locals.target = (req.body.serviceAssignmentInput === '') ? req.body.serviceAssignmentSelector : req.body.serviceAssignmentInput + res.locals.tools = toolsToBeChanged + next() +} +/** + * + * @param {string} searchTerm search target + * @param {string} searchField optional, key to search - if not provided, will search all fields + * @returns {object} + */ +async function lookupTool(searchTerm) { + searchTerm = searchTerm.toUpperCase() + let result = await Tool.findOne({ serialNumber: { $eq: searchTerm } }) + if (!result) { + result = await Tool.findOne({ barcode: { $eq: searchTerm } }) } - /** - * - * @param {string} searchTerm search target - * @param {string} searchField optional, key to search - if not provided, will search all fields - * @returns {object} - */ - async function lookupTool(searchTerm) { - searchTerm = searchTerm.toUpperCase() - let result = await Tool.findOne({ serialNumber: { $eq: searchTerm } }) - if (!result) { - result = await Tool.findOne({ barcode: { $eq: searchTerm } }) - } - if (!result) { - result = await Tool.findOne({ toolID: { $eq: searchTerm } }) - } - if (!result) { - result = {} - } - console.log(result) - return result + if (!result) { + result = await Tool.findOne({ toolID: { $eq: searchTerm } }) } - /** - * @name lookupToolWrapper - * @desc iterator for looking up multiple search terms for checkTools - * @param {*} searchTerms - * @return {*} array of tools, with dummy objects if nothing is found - */ - async function lookupToolWrapper(searchTerms) { - const tools = [] - for (let i = 0; i < searchTerms.length; i++) { - const result = await lookupTool(searchTerms[i]) - console.log(result) - if (result?.serialNumber === undefined) { - tools.push({ - serialNumber: searchTerms[i] - }) - } else tools.push(result) - } - return tools + if (!result) { + result = {} + } + console.log(result) + return result +} +/** + * @name lookupToolWrapper + * @desc iterator for looking up multiple search terms for checkTools + * @param {*} searchTerms + * @return {*} array of tools, with dummy objects if nothing is found + */ +async function lookupToolWrapper(searchTerms) { + const tools = [] + for (let i = 0; i < searchTerms.length; i++) { + const result = await lookupTool(searchTerms[i]) + console.log(result) + if (result?.serialNumber === undefined) { + tools.push({ + serialNumber: searchTerms[i] + }) + } else tools.push(result) } + return tools +} - async function submitCheckInOut(req, res, next) { - const id = mutateToArray(req.body.id) - const newServiceAssignment = mutateToArray(req.body.newServiceAssignment) - const newTools = [] - for (let i = 0; i < id.length; i++) { - if (id[i] === 'toolNotFound') break - updateToolHistory(id[i]) - newTools.push( - await Tool.findByIdAndUpdate( - { _id: id[i] }, - { - serviceAssignment: newServiceAssignment[i], - $inc: { __v: 1 }, - $set: { updatedAt: Date.now() } - }, - { new: true } - ) +async function submitCheckInOut(req, res, next) { + const id = mutateToArray(req.body.id) + const newServiceAssignment = mutateToArray(req.body.newServiceAssignment) + const newTools = [] + for (let i = 0; i < id.length; i++) { + if (id[i] === 'toolNotFound') break + updateToolHistory(id[i]) + newTools.push( + await Tool.findByIdAndUpdate( + { _id: id[i] }, + { + serviceAssignment: newServiceAssignment[i], + $inc: { __v: 1 }, + $set: { updatedAt: Date.now() } + }, + { new: true } ) - } - res.locals.tools = newTools - res.locals.message = `${newTools.length} tool(s) have been updated` - next() + ) } + res.locals.tools = newTools + res.locals.message = `${newTools.length} tool(s) have been updated` + next() +} - const generatePrinterFriendlyToolList = async (req, res, next) => { - try { - if (!res.locals.tools) return next() - const { tools } = res.locals - const printerFriendlyToolArray = await tools.map((tool) => { - const { - serialNumber, - modelNumber, - toolID, - barcode, - description - } = tool - return { - serialNumber, - modelNumber, - toolID, - barcode, - description - } - }) - if (printerFriendlyToolArray?.length === 0) throw new Error('There was a problem creating the printer friendly data') - res.locals.printerFriendlyTools = printerFriendlyToolArray || [] - return next() - } catch (err) { - res.locals.message = err.message - res.locals.printerFriendlyTools = [] - return next() - } +const generatePrinterFriendlyToolList = async (req, res, next) => { + try { + if (!res.locals.tools) return next() + const { tools } = res.locals + const printerFriendlyToolArray = await tools.map((tool) => { + const { + serialNumber, + modelNumber, + toolID, + barcode, + description + } = tool + return { + serialNumber, + modelNumber, + toolID, + barcode, + description + } + }) + if (printerFriendlyToolArray?.length === 0) throw new Error('There was a problem creating the printer friendly data') + res.locals.printerFriendlyTools = printerFriendlyToolArray || [] + return next() + } catch (err) { + res.locals.message = err.message + res.locals.printerFriendlyTools = [] + return next() } +} - export { - getAllTools, - getActiveTools, - getToolByID, - searchTools, - createTool, - updateTool, - archiveTool, - checkTools, - submitCheckInOut, - generatePrinterFriendlyToolList - } +export { + getAllTools, + getActiveTools, + getToolByID, + searchTools, + createTool, + updateTool, + archiveTool, + checkTools, + submitCheckInOut, + generatePrinterFriendlyToolList +} diff --git a/src/models/Tool.model.js b/src/models/Tool.model.js index e8da2cfb..7f63e8ab 100644 --- a/src/models/Tool.model.js +++ b/src/models/Tool.model.js @@ -34,12 +34,14 @@ const toolSchema = new Schema( serviceAssignment: { type: Schema.Types.ObjectId, ref: 'ServiceAssignment', - autopopulate: true + autopopulate: true, + default: '64a34b651288871770df1086' }, category: { type: Schema.Types.ObjectId, ref: 'Category', - autopopulate: true + autopopulate: true, + default: '64a1c3d8d71e121dfd39b7ab' }, description: { type: String,