diff --git a/src/config/constants.js b/src/config/constants.js index 6ccf7a5..8e66871 100644 --- a/src/config/constants.js +++ b/src/config/constants.js @@ -12,6 +12,17 @@ const UserActions = [ "Receive", ]; +const LevelPositions = [ + "LDB", + "LDF", + "LUB", + "LUF", + "RDB", + "RDF", + "RUB", + "RUF", +]; + const InventoryScopes = ["Inventory", "Material", "Item"]; const WarehouseScopes = [ @@ -40,6 +51,8 @@ const CustomAttributeTypes = [ "Enumerable", ]; +const SublevelInventoryTypes = ["Inventory", "Material", "Item"]; + const AUTHENTICATION_FAILURE_ERROR_MESSAGE = "Authentication Failed!"; const AUTHORIZATION_FAILURE_ERROR_MESSAGE = "User not permitted due to lack of access!"; @@ -52,7 +65,9 @@ module.exports = { WarehouseScopes, InventoryTypes, CustomAttributeTypes, + SublevelInventoryTypes, SubLevelTypes, + LevelPositions, SUPER_ADMIN_ROLE: "super-admin", COMPANY_ADMIN_ROLE: "company-admin", WAREHOUSE_ADMIN_ROLE: "warehouse-admin", diff --git a/src/controller/index.js b/src/controller/index.js index 82baf2a..b8183d3 100644 --- a/src/controller/index.js +++ b/src/controller/index.js @@ -11,6 +11,7 @@ const areaRouter = require("./area.router"); const bayRouter = require("./bay.router"); const rowRouter = require("./row.router"); const levelRouter = require("./level.router"); +const sublevelRouter = require("./sublevel.router"); const dashboardRouter = require("./dashboard.router"); router.use("/user-role", AuthenticateMiddleware, userRoleRouter); @@ -23,6 +24,7 @@ router.use("/area", areaRouter); router.use("/bay", bayRouter); router.use("/row", rowRouter); router.use("/level", levelRouter); +router.use("/sublevel", sublevelRouter); router.use("/dashboard", dashboardRouter); router.get("/", (req, res) => { diff --git a/src/controller/sublevel.controller.js b/src/controller/sublevel.controller.js new file mode 100644 index 0000000..a62b36b --- /dev/null +++ b/src/controller/sublevel.controller.js @@ -0,0 +1,154 @@ +const Sublevel = require("../models/Sublevel"); +const mongoose = require("mongoose"); +const { addSublevelToParent, deleteSubLevelTreeFromRoot, validPositions } = require("./utils/sublevel"); +const { SubLevelTypes } = require("../config/constants"); + +module.exports = { + /** + * Gets the sublevel data by `id` + */ + getSubLevelByID: async (req, res, next) => { + const { id } = req.params; + + if (!id) { + res.status(400).send("Missing id param"); + return; + } + + try { + const subLevelData = await Sublevel.findById(id); + if (!subLevelData) { + res.status(404); + return; + } + req.send(subLevelData); + } catch (error) { + next(error); + } + }, + + /** + * Create a sublevel + */ + createSubLevel: async (req, res, next) => { + const { name, type, specs, parent_id, parentIsLevel, positions } = req.body; + + if (!(name && type && parent_id && positions)) { + res.status(400).send("Missing params param"); + return; + } + + if (!SubLevelTypes.includes(type)) { + res.status(400).send("Invalid type"); + return; + } + + if (!validPositions(positions)) { + res.status(400).send("Invalid positions"); + return; + } + + try { + const parentData = parentIsLevel ? { parent_current_depth: 0, parent_main_level_id: parent_id } : await Sublevel.findById(parent_id); + + const { parent_current_depth, parent_main_level_id } = parentData; + + const sublevelData = new Sublevel({ + name, + type: type, + specs, + main_level_id: parent_main_level_id, + current_depth: parent_current_depth + 1, + parent_sublevel_id: mongoose.Types.ObjectId(parent_id), + preffered_inventory: [], + }); + + await addSublevelToParent({ type, positions, sub_level_id: sublevelData._id.toString() }, parent_id, parentIsLevel); + + await sublevelData.save(); + if (!sublevelData) { + res.status(404); + return; + } + req.send(sublevelData); + } catch (error) { + next(error); + } + }, + + /** + * Update a sublevels' detail + */ + updateSubLevelDetailsByID: async (req, res, next) => { + const { name, type, specs } = req.body; + const { id } = req.params; + + if (!(name || type || specs)) { + res.status(400).send("Missing params param"); + return; + } + + if (type && !SubLevelTypes.includes(type)) { + res.status(400).send("Invalid type"); + return; + } + + try { + const sublevelData = await Sublevel.findById(id); + if (!sublevelData) { + res.status(404); + return; + } + + if (name) sublevelData.name = name; + if (type) sublevelData.type = type; + if (specs) sublevelData.specs = specs; + + const newData = await sublevelData.save(); + req.send(newData); + } catch (error) { + next(error); + } + }, + + /** + * Deletes a sublevel + */ + deleteSublevel: async (req, res, next) => { + const { id } = req.params; + if (!id) { + res.status(404).send("Provide an ID"); + } + + try { + const deletedSublevels = deleteSubLevelTreeFromRoot(id); + res.send({ success: deletedSublevels.length, data: { deletedSublevels: deletedSublevels } }); + } catch (err) { + next(err); + } + }, + + /** + * Add preffered_inventory to a sublevel + */ + addInventory: async (req, res, next) => { + const { id, inventory } = req.body; + const sublevelData = await Sublevel.findById(id); + if (!sublevelData) { + res.status(404); + return; + } + // eslint-disable-next-line no-prototype-builtins + if (inventory.every((_) => _.hasOwnProperty("id") && _.hasOwnProperty("type"))) { + res.status(404).send({ success: false, message: "invalid inventory data" }); + return; + } + try { + sublevelData.preffered_inventory.push(...inventory); + await sublevelData.save(); + res.send({ success: true, data: sublevelData.preffered_inventory }); + } catch (err) { + next(err); + } + }, +}; diff --git a/src/controller/sublevel.router.js b/src/controller/sublevel.router.js new file mode 100644 index 0000000..3f4a4e2 --- /dev/null +++ b/src/controller/sublevel.router.js @@ -0,0 +1,29 @@ +const router = require("express").Router(); +const controller = require("./sublevel.controller"); + +/** + * @route /sublevel/:id + */ +router.get("/:id", controller.getSubLevelByID); + +/** + * @route /sublevel/ + */ +router.post("/", controller.createSubLevel); + +/** + * @route /sublevel/:id + */ +router.patch("/:id", controller.updateSubLevelDetailsByID); + +/** + * @route /sublevel/:id + */ +router.delete("/:id", controller.deleteSublevel); + +/** + * @route /sublevel/add-inventory + */ +router.post("/add-inventory", controller.addInventory); + +module.exports = router; diff --git a/src/controller/utils/sublevel.js b/src/controller/utils/sublevel.js new file mode 100644 index 0000000..3e63a93 --- /dev/null +++ b/src/controller/utils/sublevel.js @@ -0,0 +1,114 @@ +const Sublevel = require("../../models/Sublevel"); +const Level = require("../../models/Level"); +const { LevelPositions } = require("../../config/constants"); + +/** + * To move a sub level + * @param {string} sub_level_id The Sublevel to me moved + * @param {string} parent_sub_or_level_id The Level/Sublevel under which to be moved + * @returns {{success: boolean, message: string}} + */ +const moveSublevel = async (sub_level_id, parent_sub_or_level_id) => { + /** + * - Check if depths of parent_sub_level_id and parent_sub_or_level_id are same + * - Copy and add references + * - Delete and remove references + */ + return { success: false, message: "not implemented" }; +}; + +/** + * To delete a sub level and all corresponding sub levels under it + * @param {string} root_sub_level_id The root Sublevel ID + * @returns {string[]} The Sublevel IDs that have been deleted + */ +const deleteSubLevelTreeFromRoot = async (root_sub_level_id) => { + let sub_level_ids = []; + let temp_sub_level_ids = [root_sub_level_id]; + + // remove from parent first + await removeSublevelFromParent(root_sub_level_id); + + while (temp_sub_level_ids.length > 0) { + const level_sub_level_data = await Sublevel.find({ + _id: temp_sub_level_ids, + }); + + sub_level_ids = [...sub_level_ids, ...temp_sub_level_ids]; + temp_sub_level_ids = []; + + level_sub_level_data.forEach((sub_level_data) => { + sub_level_data.sub_levels.forEach((sub_level) => { + temp_sub_level_ids.push(sub_level.sub_level_id.toString()); + }); + }); + } + + await Sublevel.deleteMany({ _id: sub_level_ids }); + console.log("Deleting sub-level tree", { sub_level_ids }); + return sub_level_ids; +}; + +/** + * Add the sublevel data to the parent document + * @param {{type: string, positions: string[], sub_level_id: string}} payload The sublevel data + * @param {string} parent_id The parent level ID + * @param {boolean} parentIsLevel Is parent a level? + */ +const addSublevelToParent = async (payload, parent_id, parentIsLevel) => { + if (parentIsLevel) { + // add sublevel to parent + const parentData = await Sublevel.findById(parent_id); + parentData.sub_levels.push(payload); + return await parentData.save(); + } else { + // add sublevel to sublevel + const parentData = await Sublevel.findById(parent_id); + parentData.sub_levels.push(payload); + return await parentData.save(); + } +}; + +const removeSublevelFromParent = async (id) => { + const { main_level_id, parent_sublevel_id, current_depth } = await Sublevel.findById(id); + if (current_depth == 1) { + // it means parent is level + const parentData = await Level.findById(main_level_id); + parentData.sub_levels = parentData.sub_levels.filter((sub_level) => sub_level.sub_level_id != id); + } else { + // parent is another sublevel + const parentData = await Sublevel.findById(parent_sublevel_id); + parentData.sub_levels = parentData.sub_levels.filter((sub_level) => sub_level.sub_level_id != id); + } +}; + +/** + * Provides a list of available positions at the particular Level/Sublevel + * @param {object} sublevelData Level / Sublevel mongoose document + * @returns {string[]} The list of available positions + */ +const findAvailablePositions = (sublevelData) => { + let positionsOccupied = []; + sublevelData.sub_levels.forEach((sublevel) => { + positionsOccupied = [...positionsOccupied, ...sublevel.sub_levels]; + }); + return LevelPositions.filter((pos) => !positionsOccupied.includes(pos)); +}; + +/** + * Check if positions are valid positions + * @param {string[]} positions An array of positions + * @returns {boolean} + */ +const validPositions = (positions) => { + return positions.every((position) => LevelPositions.includes(position)); +}; + +module.exports = { + addSublevelToParent, + removeSublevelFromParent, + deleteSubLevelTreeFromRoot, + moveSublevel, + findAvailablePositions, + validPositions, +}; diff --git a/src/models/Level.js b/src/models/Level.js index debc961..c33565d 100644 --- a/src/models/Level.js +++ b/src/models/Level.js @@ -1,5 +1,5 @@ const mongoose = require("mongoose"); -const { SubLevelTypes } = require("../config/constants"); +const { SubLevelTypes, LevelPositions } = require("../config/constants"); const schema = new mongoose.Schema( { @@ -14,8 +14,7 @@ const schema = new mongoose.Schema( trim: true, }, specs: { - // TODO: TBD - type: String, + type: Object, trim: true, }, bay_id: { @@ -27,18 +26,14 @@ const schema = new mongoose.Schema( type: { required: true, type: String, - enum: SubLevelTypes - }, - depth: { - required: true, - type: Number, - min: 1, // Level is at 0 - max: 5, - }, - bay_id: { - type: mongoose.Schema.Types.ObjectId, - ref: "Bay", + enum: SubLevelTypes, }, + postitions: [ + { + type: String, + enum: LevelPositions, + }, + ], sub_level_id: { required: true, type: mongoose.Schema.Types.ObjectId, diff --git a/src/models/Sublevel.js b/src/models/Sublevel.js index 172e538..b32bee0 100644 --- a/src/models/Sublevel.js +++ b/src/models/Sublevel.js @@ -1,5 +1,5 @@ const mongoose = require("mongoose"); -const { SubLevelTypes } = require("../config/constants"); +const { SubLevelTypes, LevelPositions, SublevelInventoryTypes } = require("../config/constants"); const schema = new mongoose.Schema( { @@ -11,11 +11,10 @@ const schema = new mongoose.Schema( type: { type: String, required: true, - enum: SubLevelTypes + enum: SubLevelTypes, }, specs: { - // TBD - type: String, + type: Object, trim: true, }, main_level_id: { @@ -39,12 +38,12 @@ const schema = new mongoose.Schema( type: String, enum: SubLevelTypes, }, - depth: { - required: true, - type: Number, - min: 1, // Level is at 0 - max: 5, - }, + postitions: [ + { + type: String, + enum: LevelPositions, + }, + ], sub_level_id: { required: true, type: mongoose.Schema.Types.ObjectId, @@ -56,10 +55,16 @@ const schema = new mongoose.Schema( type: Boolean, default: false, }, - inventory: [ + preffered_inventory: [ { - type: mongoose.Schema.Types.ObjectId, - ref: "Inventory", + id: { + type: mongoose.Schema.Types.ObjectId, + refPath: "type", + }, + type: { + type: String, + enum: SublevelInventoryTypes, + }, }, ], },