diff --git a/src/controllers/bmdashboard/bmInventoryTypeController.js b/src/controllers/bmdashboard/bmInventoryTypeController.js index b9ced243e..76029a42b 100644 --- a/src/controllers/bmdashboard/bmInventoryTypeController.js +++ b/src/controllers/bmdashboard/bmInventoryTypeController.js @@ -1,7 +1,7 @@ -const bmInventoryTypeController = function (InvType) { - const fetchMaterialTypes = async (req, res) => { +function bmInventoryTypeController(InvType, MatType, ConsType, ReusType, ToolType, EquipType) { + async function fetchMaterialTypes(req, res) { try { - InvType + MatType .find() .exec() .then(result => res.status(200).send(result)) @@ -9,11 +9,92 @@ const bmInventoryTypeController = function (InvType) { } catch (err) { res.json(err); } - }; + } + + async function addEquipmentType(req, res) { + const { + name, + desc: description, + fuel: fuelType, + requestor: { requestorId }, + } = req.body; + try { + EquipType + .find({ name }) + .then((result) => { + if (result.length) { + res.status(409).send(); + } else { + const newDoc = { + category: 'Equipment', + name, + description, + fuelType, + createdBy: requestorId, + }; + EquipType + .create(newDoc) + .then(() => res.status(201).send()) + .catch((error) => { + if (error._message.includes('validation failed')) { + res.status(400).send(error); + } else { + res.status(500).send(error); + } + }); + } + }) + .catch(error => res.status(500).send(error)); + } catch (error) { + res.status(500).send(error); + } + } + const fetchSingleInventoryType = async (req, res) => { + const { invtypeId } = req.params; + try { + const result = await InvType.findById(invtypeId).exec(); + res.status(200).send(result); + } catch (error) { + res.status(500).send(error); + } + }; + + const updateNameAndUnit = async (req, res) => { + try { + const { invtypeId } = req.params; + const { name, unit } = req.body; + + const updateData = {}; + + if (name) { + updateData.name = name; + } + + if (unit) { + updateData.unit = unit; + } + + const updatedInvType = await InvType.findByIdAndUpdate( + invtypeId, + updateData, + { new: true, runValidators: true }, + ); + + if (!updatedInvType) { + return res.status(404).json({ error: 'invType Material not found check Id' }); + } + res.status(200).json(updatedInvType); + } catch (error) { + res.status(500).send(error); + } + }; return { fetchMaterialTypes, + addEquipmentType, + fetchSingleInventoryType, + updateNameAndUnit, }; -}; +} module.exports = bmInventoryTypeController; diff --git a/src/controllers/bmdashboard/bmToolController.js b/src/controllers/bmdashboard/bmToolController.js new file mode 100644 index 000000000..0feec256c --- /dev/null +++ b/src/controllers/bmdashboard/bmToolController.js @@ -0,0 +1,52 @@ +const bmToolController = (BuildingTool) => { + const fetchSingleTool = async (req, res) => { + const { toolId } = req.params; + try { + BuildingTool + .findById(toolId) + .populate([ + { + path: 'itemType', + select: '_id name description unit imageUrl category', + }, + { + path: 'userResponsible', + select: '_id firstName lastName', + }, + { + path: 'purchaseRecord', + populate: { + path: 'requestedBy', + select: '_id firstName lastName', + }, + }, + { + path: 'updateRecord', + populate: { + path: 'createdBy', + select: '_id firstName lastName', + }, + }, + { + path: 'logRecord', + populate: [{ + path: 'createdBy', + select: '_id firstName lastName', + }, + { + path: 'responsibleUser', + select: '_id firstName lastName', + }], + }, + ]) + .exec() + .then(tool => res.status(200).send(tool)) + .catch(error => res.status(500).send(error)); + } catch (err) { + res.json(err); + } + }; + return { fetchSingleTool }; +}; + +module.exports = bmToolController; diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index 9bcf071de..126d17914 100644 --- a/src/controllers/taskController.js +++ b/src/controllers/taskController.js @@ -1,10 +1,10 @@ -const mongoose = require('mongoose'); -const wbs = require('../models/wbs'); -const timeEntryHelper = require('../helpers/timeEntryHelper')(); -const taskHelper = require('../helpers/taskHelper')(); -const { hasPermission } = require('../utilities/permissions'); -const emailSender = require('../utilities/emailSender'); -const userProfile = require('../models/userProfile'); +const mongoose = require("mongoose"); +const WBS = require("../models/wbs"); +const UserProfile = require("../models/userProfile"); +const timeEntryHelper = require("../helpers/timeEntryHelper")(); +const taskHelper = require("../helpers/taskHelper")(); +const { hasPermission } = require("../utilities/permissions"); +const emailSender = require("../utilities/emailSender"); const taskController = function (Task) { const getTasks = (req, res) => { @@ -17,7 +17,7 @@ const taskController = function (Task) { const { mother } = req.params; - if (mother !== '0') { + if (mother !== "0") { query = { wbsId: { $in: [req.params.wbsId] }, level: { $in: [level] }, @@ -26,16 +26,16 @@ const taskController = function (Task) { } Task.find(query) - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); + .then((results) => res.status(200).send(results)) + .catch((error) => res.status(404).send(error)); }; const getWBSId = (req, res) => { const { wbsId } = req.params; - wbs.findById(wbsId) - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); + WBS.findById(wbsId) + .then((results) => res.status(200).send(results)) + .catch((error) => res.status(404).send(error)); }; const updateSumUp = ( @@ -45,7 +45,7 @@ const taskController = function (Task) { hoursMost, hoursLogged, estimatedHours, - resources, + resources ) => { Task.findById(taskId, (error, task) => { task.hoursBest = hoursBest; @@ -81,10 +81,10 @@ const taskController = function (Task) { }; const calculateSubTasks = (level, tasks) => { - const parentTasks = tasks.filter(task => task.level === level); + const parentTasks = tasks.filter((task) => task.level === level); parentTasks.forEach((task) => { const childTasks = tasks.filter( - taskChild => taskChild.level === level + 1, + (taskChild) => taskChild.level === level + 1 ); let sumHoursBest = 0; let sumHoursWorst = 0; @@ -96,14 +96,19 @@ const taskController = function (Task) { childTasks.forEach((childTask) => { if (childTask.mother.equals(task.taskId)) { hasChild = true; - sumHoursBest = parseFloat(childTask.hoursBest, 10) + parseFloat(sumHoursBest, 10); - sumHoursWorst = parseFloat(childTask.hoursWorst, 10) - + parseFloat(sumHoursWorst, 10); - sumHoursMost = parseFloat(childTask.hoursMost, 10) + parseFloat(sumHoursMost, 10); - sumHoursLogged = parseFloat(childTask.hoursLogged, 10) - + parseFloat(sumHoursLogged, 10); - sumEstimatedHours = parseFloat(childTask.estimatedHours, 10) - + parseFloat(sumEstimatedHours, 10); + sumHoursBest = + parseFloat(childTask.hoursBest, 10) + parseFloat(sumHoursBest, 10); + sumHoursWorst = + parseFloat(childTask.hoursWorst, 10) + + parseFloat(sumHoursWorst, 10); + sumHoursMost = + parseFloat(childTask.hoursMost, 10) + parseFloat(sumHoursMost, 10); + sumHoursLogged = + parseFloat(childTask.hoursLogged, 10) + + parseFloat(sumHoursLogged, 10); + sumEstimatedHours = + parseFloat(childTask.estimatedHours, 10) + + parseFloat(sumEstimatedHours, 10); childTask.resources.forEach((member) => { let isInResource = false; resources.forEach((mem) => { @@ -136,7 +141,7 @@ const taskController = function (Task) { sumHoursMost, sumHoursLogged, sumEstimatedHours, - resources, + resources ); } }); @@ -144,10 +149,10 @@ const taskController = function (Task) { }; const setDatesSubTasks = (level, tasks) => { - const parentTasks = tasks.filter(task => task.level === level); + const parentTasks = tasks.filter((task) => task.level === level); parentTasks.forEach((task) => { const childTasks = tasks.filter( - taskChild => taskChild.level === level + 1, + (taskChild) => taskChild.level === level + 1 ); let minStartedDate = task.startedDatetime; let maxDueDatetime = task.dueDatetime; @@ -178,10 +183,10 @@ const taskController = function (Task) { }; const calculatePriority = (level, tasks) => { - const parentTasks = tasks.filter(task => task.level === level); + const parentTasks = tasks.filter((task) => task.level === level); parentTasks.forEach((task) => { const childTasks = tasks.filter( - taskChild => taskChild.level === level + 1, + (taskChild) => taskChild.level === level + 1 ); let totalNumberPriority = 0; let totalChild = 0; @@ -190,11 +195,11 @@ const taskController = function (Task) { if (childTask.mother.equals(task.taskId)) { hasChild = true; totalChild += 1; - if (childTask.priority === 'Primary') { + if (childTask.priority === "Primary") { totalNumberPriority += 3; - } else if (childTask.priority === 'Secondary') { + } else if (childTask.priority === "Secondary") { totalNumberPriority += 2; - } else if (childTask.priority === 'Tertiary') { + } else if (childTask.priority === "Tertiary") { totalNumberPriority += 1; } } @@ -207,11 +212,11 @@ const taskController = function (Task) { if (mainTask._id.equals(task._id)) { const avg = totalNumberPriority / totalChild; if (avg <= 1.6) { - priority = 'Tertiary'; + priority = "Tertiary"; } else if (avg > 1.6 && avg < 2.5) { - priority = 'Secondary'; + priority = "Secondary"; } else { - priority = 'Primary'; + priority = "Primary"; } } }); @@ -222,10 +227,10 @@ const taskController = function (Task) { }; const setAssigned = (level, tasks) => { - const parentTasks = tasks.filter(task => task.level === level); + const parentTasks = tasks.filter((task) => task.level === level); parentTasks.forEach((task) => { const childTasks = tasks.filter( - taskChild => taskChild.level === level + 1, + (taskChild) => taskChild.level === level + 1 ); let isAssigned = false; let hasChild = false; @@ -259,7 +264,7 @@ const taskController = function (Task) { { wbsId: { $in: [wbsId] } }, ], }).then((tasks) => { - tasks = [...new Set(tasks.map(item => item))]; + tasks = [...new Set(tasks.map((item) => item))]; for (let lv = 3; lv > 0; lv -= 1) { calculateSubTasks(lv, tasks); setDatesSubTasks(lv, tasks); @@ -285,18 +290,20 @@ const taskController = function (Task) { const tasksWithId = tasks.map((task) => { const _id = new mongoose.Types.ObjectId(); const resources = task.resources.map((resource) => { - const [name, userID, profilePic] = resource.split('|'); + const [name, userID, profilePic] = resource.split("|"); return { name, userID, profilePic }; }); return { - ...task, _id, resources, + ...task, + _id, + resources, }; }); // update tasks makes sure its parentIds and mother props are correct assigned, tasksWithId.forEach((task) => { - const taskNumArr = task.num.split('.'); + const taskNumArr = task.num.split("."); switch (task.level) { case 1: // task.num is x, no parentId1 or mother task.parentId1 = null; // no parent so its value is null @@ -305,21 +312,34 @@ const taskController = function (Task) { task.mother = null; break; case 2: // task.num is x.x, only has one level of parent (x) - task.parentId1 = tasksWithId.find(pTask => pTask.num === taskNumArr[0])._id; // task of parentId1 has num prop of x + task.parentId1 = tasksWithId.find( + (pTask) => pTask.num === taskNumArr[0] + )._id; // task of parentId1 has num prop of x task.parentId2 = null; task.parentId3 = null; task.mother = task.parentId1; // parent task num prop is x break; case 3: // task.num is x.x.x, has two levels of parent (parent: x.x and grandparent: x) - task.parentId1 = tasksWithId.find(pTask => pTask.num === taskNumArr[0])._id; // task of parentId1 has num prop of x - task.parentId2 = tasksWithId.find(pTask => pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}`)._id; // task of parentId2 has num prop of x.x + task.parentId1 = tasksWithId.find( + (pTask) => pTask.num === taskNumArr[0] + )._id; // task of parentId1 has num prop of x + task.parentId2 = tasksWithId.find( + (pTask) => pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}` + )._id; // task of parentId2 has num prop of x.x task.parentId3 = null; task.mother = task.parentId2; // parent task num prop is x.x break; case 4: // task.num is x.x.x.x, has three levels of parent (x.x.x, x.x and x) - task.parentId1 = tasksWithId.find(pTask => pTask.num === taskNumArr[0])._id; // x - task.parentId2 = tasksWithId.find(pTask => pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}`)._id; // x.x - task.parentId3 = tasksWithId.find(pTask => pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}.${taskNumArr[2]}`)._id; // x.x.x + task.parentId1 = tasksWithId.find( + (pTask) => pTask.num === taskNumArr[0] + )._id; // x + task.parentId2 = tasksWithId.find( + (pTask) => pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}` + )._id; // x.x + task.parentId3 = tasksWithId.find( + (pTask) => + pTask.num === `${taskNumArr[0]}.${taskNumArr[1]}.${taskNumArr[2]}` + )._id; // x.x.x task.mother = task.parentId3; // parent task num prop is x.x.x break; default: @@ -327,7 +347,9 @@ const taskController = function (Task) { }); // create an array of four empty arrays - const tasksFromSameLevelArr = Array(4).fill(null).map(() => []); + const tasksFromSameLevelArr = Array(4) + .fill(null) + .map(() => []); // sort them out into an array of four arrays based on their levels tasksWithId.forEach((task) => { @@ -358,18 +380,32 @@ const taskController = function (Task) { task.hoursMost += childTask.hoursMost; task.hoursLogged += childTask.hoursLogged; task.estimatedHours += childTask.estimatedHours; - task.startedDatetime = Math.min(task.startedDatetime, childTask.startedDatetime); - task.dueDatetime = Math.max(task.dueDatetime, childTask.dueDatetime); + task.startedDatetime = Math.min( + task.startedDatetime, + childTask.startedDatetime + ); + task.dueDatetime = Math.max( + task.dueDatetime, + childTask.dueDatetime + ); task.childrenQty = (task.childrenQty || 0) + 1; task.isAssigned = task.isAssigned || childTask.isAssigned; - task.resources = childTask.resources.reduce((resources, childTaskMember) => { - if (task.resources.every(member => member.name !== childTaskMember.name)) return [...resources, childTaskMember]; - return resources; - }, [...task.resources]); + task.resources = childTask.resources.reduce( + (resources, childTaskMember) => { + if ( + task.resources.every( + (member) => member.name !== childTaskMember.name + ) + ) + return [...resources, childTaskMember]; + return resources; + }, + [...task.resources] + ); // add priority pts for task.priority - if (childTask.priority === 'Primary') { + if (childTask.priority === "Primary") { priorityPts += 3; - } else if (childTask.priority === 'Secondary') { + } else if (childTask.priority === "Secondary") { priorityPts += 2; } else { priorityPts += 1; @@ -379,11 +415,11 @@ const taskController = function (Task) { }); const averagePts = priorityPts / task.childrenQty; if (averagePts >= 2.5) { - task.priority = 'Primary'; + task.priority = "Primary"; } else if (averagePts >= 1.6) { - task.priority = 'Secondary'; + task.priority = "Secondary"; } else { - task.priority = 'Tertiary'; + task.priority = "Tertiary"; } }); } @@ -392,10 +428,10 @@ const taskController = function (Task) { }; const importTask = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'importTask')) { + if (!(await hasPermission(req.body.requestor, "importTask"))) { res .status(403) - .send({ error: 'You are not authorized to create new Task.' }); + .send({ error: "You are not authorized to create new Task." }); return; } @@ -407,7 +443,10 @@ const taskController = function (Task) { const createdDatetime = Date.now(); const modifiedDatetime = Date.now(); const _task = new Task({ - ...task, wbsId, createdDatetime, modifiedDatetime, + ...task, + wbsId, + createdDatetime, + modifiedDatetime, }); _task @@ -418,20 +457,20 @@ const taskController = function (Task) { }); }); - res.status(201).send('done'); + res.status(201).send("done"); }; const postTask = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'postTask')) { + if (!(await hasPermission(req.body.requestor, "postTask"))) { res .status(403) - .send({ error: 'You are not authorized to create new Task.' }); + .send({ error: "You are not authorized to create new Task." }); return; } if (!req.body.taskName || !req.body.isActive) { res.status(400).send({ - error: 'Task Name, Active status, Task Number are mandatory fields', + error: "Task Name, Active status, Task Number are mandatory fields", }); return; } @@ -442,31 +481,35 @@ const taskController = function (Task) { const modifiedDatetime = Date.now(); const _task = new Task({ - ...task, wbsId, createdDatetime, modifiedDatetime, + ...task, + wbsId, + createdDatetime, + modifiedDatetime, }); const saveTask = _task.save(); - const saveWbs = wbs.findById(wbsId).then((currentwbs) => { + const saveWbs = WBS.findById(wbsId).then((currentwbs) => { currentwbs.modifiedDatetime = Date.now(); return currentwbs.save(); }); - Promise.all([saveTask, saveWbs]).then(results => res.status(201).send(results[0])) + Promise.all([saveTask, saveWbs]) + .then((results) => res.status(201).send(results[0])) .catch((errors) => { res.status(400).send(errors); }); }; const updateNum = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'updateNum')) { + if (!(await hasPermission(req.body.requestor, "updateNum"))) { res .status(403) - .send({ error: 'You are not authorized to create new projects.' }); + .send({ error: "You are not authorized to create new projects." }); return; } if (!req.body.nums) { - res.status(400).send({ error: 'Num is a mandatory fields' }); + res.status(400).send({ error: "Num is a mandatory fields" }); return; } @@ -477,7 +520,7 @@ const taskController = function (Task) { task .save() .then() - .catch(errors => res.status(400).send(errors)); + .catch((errors) => res.status(400).send(errors)); }); // level 2 @@ -487,13 +530,13 @@ const taskController = function (Task) { childTasks1.forEach((childTask1) => { childTask1.num = childTask1.num.replace( childTask1.num.substring(0, elm.num.length), - elm.num, + elm.num ); childTask1 .save() .then(true) - .catch(errors => res.status(400).send(errors)); + .catch((errors) => res.status(400).send(errors)); // level 3 Task.find({ parentId: { $in: [childTask1._id] } }) @@ -502,13 +545,13 @@ const taskController = function (Task) { childTasks2.forEach((childTask2) => { childTask2.num = childTask2.num.replace( childTask2.num.substring(0, childTask1.num.length), - childTask1.num, + childTask1.num ); childTask2 .save() .then(true) - .catch(errors => res.status(400).send(errors)); + .catch((errors) => res.status(400).send(errors)); // level 4 Task.find({ parentId: { $in: [childTask2._id] } }) @@ -518,27 +561,29 @@ const taskController = function (Task) { childTask3.num = childTask3.num.replace( childTask3.num.substring( 0, - childTask2.num.length, + childTask2.num.length ), - childTask2.num, + childTask2.num ); childTask3 .save() .then(true) - .catch(errors => res.status(400).send(errors)); + .catch((errors) => + res.status(400).send(errors) + ); }); } }) - .catch(error => res.status(404).send(error)); + .catch((error) => res.status(404).send(error)); }); } }) - .catch(error => res.status(404).send(error)); + .catch((error) => res.status(404).send(error)); }); } }) - .catch(error => res.status(404).send(error)); + .catch((error) => res.status(404).send(error)); }); res.status(200).send(true); @@ -548,106 +593,113 @@ const taskController = function (Task) { if (!req.body.fromNum || !req.body.toNum) { res .status(400) - .send({ error: 'wbsId, fromNum, toNum are mandatory fields' }); + .send({ error: "wbsId, fromNum, toNum are mandatory fields" }); return; } Task.find({ wbsId: { $in: req.params.wbsId } }).then((tasks) => { - const fromNumArr = req.body.fromNum.replace(/\.0/g, '').split('.'); - const toNumArr = req.body.toNum.replace(/\.0/g, '').split('.'); + const fromNumArr = req.body.fromNum.replace(/\.0/g, "").split("."); + const toNumArr = req.body.toNum.replace(/\.0/g, "").split("."); const changedLvl = fromNumArr.length; const fromLastLvl = parseInt(fromNumArr.pop(), 10); const toLastLvl = parseInt(toNumArr.pop(), 10); - const leadingLvls = fromNumArr.length ? fromNumArr.join('.').concat('.') : ''; // in a format of x, x.x, or x.x.x, also could be '' if move level one tasks + const leadingLvls = fromNumArr.length + ? fromNumArr.join(".").concat(".") + : ""; // in a format of x, x.x, or x.x.x, also could be '' if move level one tasks const changingNums = []; - for (let i = Math.min(fromLastLvl, toLastLvl); i <= Math.max(fromLastLvl, toLastLvl); i += 1) { + for ( + let i = Math.min(fromLastLvl, toLastLvl); + i <= Math.max(fromLastLvl, toLastLvl); + i += 1 + ) { changingNums.push(leadingLvls.concat(`${i}`)); } const changingNumTasks = tasks.filter((task) => { - const taskLeadingNum = task.num.split('.').slice(0, changedLvl).join('.'); + const taskLeadingNum = task.num + .split(".") + .slice(0, changedLvl) + .join("."); return changingNums.includes(taskLeadingNum); }); const queries = []; changingNumTasks.forEach((task) => { - const taskNumArr = task.num.split('.'); + const taskNumArr = task.num.split("."); const taskChanedLvlNum = parseInt(taskNumArr[changedLvl - 1], 10); let newTaskLastLvl; if (fromLastLvl > toLastLvl) { - newTaskLastLvl = taskChanedLvlNum === fromLastLvl ? toLastLvl : taskChanedLvlNum + 1; + newTaskLastLvl = + taskChanedLvlNum === fromLastLvl ? toLastLvl : taskChanedLvlNum + 1; } else { - newTaskLastLvl = taskChanedLvlNum === fromLastLvl ? toLastLvl : taskChanedLvlNum - 1; + newTaskLastLvl = + taskChanedLvlNum === fromLastLvl ? toLastLvl : taskChanedLvlNum - 1; } taskNumArr[changedLvl - 1] = String(newTaskLastLvl); - task.num = taskNumArr.join('.'); + task.num = taskNumArr.join("."); queries.push(task.save()); }); Promise.all(queries) - .then(() => res.status(200).send('Success!')) - .catch(err => res.status(400).send(err)); + .then(() => res.status(200).send("Success!")) + .catch((err) => res.status(400).send(err)); }); }; const deleteTask = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'deleteTask')) { - res - .status(403) - .send({ error: 'You are not authorized to deleteTasks.' }); + if (!(await hasPermission(req.body.requestor, "deleteTask"))) { + res.status(403).send({ error: "You are not authorized to deleteTasks." }); return; } const { taskId } = req.params; const { mother } = req.params; - const removeChildTasks = Task.find( - { - $or: [ - { _id: taskId }, - { parentId1: taskId }, - { parentId2: taskId }, - { parentId3: taskId }, - ], - }, - ) - .then((record) => { - if (!record || record === null || record.length === 0) return res.status(400).send({ error: 'No valid records found' }); - const removeTasks = record.map(rec => rec.remove()); - return removeTasks; + const removeChildTasks = Task.find({ + $or: [ + { _id: taskId }, + { parentId1: taskId }, + { parentId2: taskId }, + { parentId3: taskId }, + ], + }).then((record) => { + if (!record || record === null || record.length === 0) + return res.status(400).send({ error: "No valid records found" }); + const removeTasks = record.map((rec) => rec.remove()); + return removeTasks; }); - const updateMotherChildrenQty = mother !== 'null' - ? Task.findById(mother).then((task) => { - let newQty = 0; - let child = true; - if (task.childrenQty > 0) { - newQty = task.childrenQty - 1; - if (newQty === 0) { - child = false; + const updateMotherChildrenQty = + mother !== "null" + ? Task.findById(mother).then((task) => { + let newQty = 0; + let child = true; + if (task.childrenQty > 0) { + newQty = task.childrenQty - 1; + if (newQty === 0) { + child = false; + } } - } - task.hasChild = child; - task.childrenQty = newQty; - return task.save(); - }) - : Promise.resolve(1); + task.hasChild = child; + task.childrenQty = newQty; + return task.save(); + }) + : Promise.resolve(1); - Promise - .all([removeChildTasks, updateMotherChildrenQty]) - .then(() => res.status(200).send({ message: 'Task successfully deleted' })) // no need to resetNum(taskId, mother); - .catch(errors => res.status(400).send(errors)); + Promise.all([removeChildTasks, updateMotherChildrenQty]) + .then(() => + res.status(200).send({ message: "Task successfully deleted" }) + ) // no need to resetNum(taskId, mother); + .catch((errors) => res.status(400).send(errors)); }; const deleteTaskByWBS = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'deleteTask')) { - res - .status(403) - .send({ error: 'You are not authorized to deleteTasks.' }); + if (!(await hasPermission(req.body.requestor, "deleteTask"))) { + res.status(403).send({ error: "You are not authorized to deleteTasks." }); return; } @@ -655,7 +707,7 @@ const taskController = function (Task) { Task.find({ wbsId: { $in: [wbsId] } }, (error, record) => { if (error || !record || record === null || record.length === 0) { - res.status(400).send({ error: 'No valid records found' }); + res.status(400).send({ error: "No valid records found" }); return; } @@ -665,7 +717,9 @@ const taskController = function (Task) { }); Promise.all([...removeTasks]) - .then(() => res.status(200).send({ message: ' Tasks were successfully deleted' })) + .then(() => + res.status(200).send({ message: " Tasks were successfully deleted" }) + ) .catch((errors) => { res.status(400).send(errors); }); @@ -675,8 +729,8 @@ const taskController = function (Task) { }; const updateTask = async (req, res) => { - if (!await hasPermission(req.body.requestor, 'updateTask')) { - res.status(403).send({ error: 'You are not authorized to update Task.' }); + if (!(await hasPermission(req.body.requestor, "updateTask"))) { + res.status(403).send({ error: "You are not authorized to update Task." }); return; } @@ -684,46 +738,46 @@ const taskController = function (Task) { Task.findOneAndUpdate( { _id: mongoose.Types.ObjectId(taskId) }, - { ...req.body, modifiedDatetime: Date.now() }, + { ...req.body, modifiedDatetime: Date.now() } ) .then(() => res.status(201).send()) - .catch(error => res.status(404).send(error)); + .catch((error) => res.status(404).send(error)); }; const swap = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'swapTask')) { + if (!(await hasPermission(req.body.requestor, "swapTask"))) { res .status(403) - .send({ error: 'You are not authorized to create new projects.' }); + .send({ error: "You are not authorized to create new projects." }); return; } if (!req.body.taskId1 || !req.body.taskId2) { res .status(400) - .send({ error: 'taskId1 and taskId2 are mandatory fields' }); + .send({ error: "taskId1 and taskId2 are mandatory fields" }); return; } Task.findById(req.body.taskId1, (error1, task1) => { if (error1 || task1 === null) { - res.status(400).send('No valid records found'); + res.status(400).send("No valid records found"); return; } Task.findById(req.body.taskId2, (error2, task2) => { if (error2 || task2 === null) { - res.status(400).send('No valid records found'); + res.status(400).send("No valid records found"); return; } if (task1.parentId.toString() === task2.parentId.toString()) { - let tmpNum = ''; + let tmpNum = ""; tmpNum = task1.num; task1.num = task2.num; task2.num = tmpNum; } else { - let tmpName = ''; + let tmpName = ""; tmpName = task1.taskName; task1.taskName = task2.taskName; task2.taskName = tmpName; @@ -732,53 +786,62 @@ const taskController = function (Task) { task1 .save() .then() - .catch(errors => res.status(400).send(errors)); + .catch((errors) => res.status(400).send(errors)); task2 .save() .then() - .catch(errors => res.status(400).send(errors)); + .catch((errors) => res.status(400).send(errors)); Task.find({ wbsId: { $in: [task1.wbsId] }, }) - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); + .then((results) => res.status(200).send(results)) + .catch((error) => res.status(404).send(error)); }); }); }; const getTaskById = async (req, res) => { try { - const taskId = req.params.id; + const taskId = req.params.id; - // Ensure the task ID is provided - if (!taskId || taskId === 'undefined') { - return res.status(400).send({ error: 'Task ID is missing' }); - } + // Ensure the task ID is provided + if (!taskId || taskId === "undefined") { + return res.status(400).send({ error: "Task ID is missing" }); + } - const task = await Task.findById(taskId, '-__v -createdDatetime -modifiedDatetime'); + const task = await Task.findById( + taskId, + "-__v -createdDatetime -modifiedDatetime" + ); - if (!task) { - return res.status(400).send({ error: 'This is not a valid task' }); - } + if (!task) { + return res.status(400).send({ error: "This is not a valid task" }); + } - const hoursLogged = await timeEntryHelper.getAllHoursLoggedForSpecifiedProject(taskId); - task.set('hoursLogged', hoursLogged, { strict: false }); + const hoursLogged = + await timeEntryHelper.getAllHoursLoggedForSpecifiedProject(taskId); + task.set("hoursLogged", hoursLogged, { strict: false }); - // Fetch the resource names for all resources - const resourceNamesPromises = task.resources.map(resource => taskHelper.getUserProfileFirstAndLastName(resource.userID)); - const resourceNames = await Promise.all(resourceNamesPromises); + // Fetch the resource names for all resources + const resourceNamesPromises = task.resources.map((resource) => + taskHelper.getUserProfileFirstAndLastName(resource.userID) + ); + const resourceNames = await Promise.all(resourceNamesPromises); - // Update the task's resources with the fetched names - task.resources.forEach((resource, index) => { - resource.name = resourceNames[index] !== ' ' ? resourceNames[index] : resource.name; - }); + // Update the task's resources with the fetched names + task.resources.forEach((resource, index) => { + resource.name = + resourceNames[index] !== " " ? resourceNames[index] : resource.name; + }); - res.status(200).send(task); + res.status(200).send(task); } catch (error) { - // Generic error message, you can adjust as needed - res.status(500).send({ error: 'Internal Server Error', details: error.message }); + // Generic error message, you can adjust as needed + res + .status(500) + .send({ error: "Internal Server Error", details: error.message }); } }; @@ -787,47 +850,45 @@ const taskController = function (Task) { try { Task.find({ wbsId: { $in: [wbsId] } }).then((tasks) => { - tasks = tasks.filter(task => task.level === 1); + tasks = tasks.filter((task) => task.level === 1); tasks.forEach((task) => { updateParents(task.wbsId, task.taskId.toString()); }); - res.status(200).send('done'); + res.status(200).send("done"); }); - res.status(200).send('done'); + res.status(200).send("done"); } catch (error) { res.status(400).send(error); } }; const fixTasks = function (req, res) { - res.status(200).send('done'); + res.status(200).send("done"); }; - const getTasksByUserList = async (req, res) => { - const { members } = req.query; - const membersArr = members.split(','); + const getTasksByUserId = async (req, res) => { + const { userId } = req.params; try { Task.find( - { 'resources.userID': { $in: membersArr } }, - '-resources.profilePic', + { + "resources.userID": mongoose.Types.ObjectId(userId), + }, + "-resources.profilePic" ).then((results) => { - wbs - .find({ - _id: { $in: results.map(item => item.wbsId) }, - }) - .then((projectIds) => { - const resultsWithProjectsIds = results.map((item) => { - item.set( - 'projectId', - projectIds?.find( - projectId => projectId._id.toString() === item.wbsId.toString(), - )?.projectId, - { strict: false }, - ); - return item; - }); - res.status(200).send(resultsWithProjectsIds); + WBS.find({ + _id: { $in: results.map((item) => item.wbsId) }, + }).then((WBSs) => { + const resultsWithProjectsIds = results.map((item) => { + item.set( + "projectId", + WBSs?.find((wbs) => wbs._id.toString() === item.wbsId.toString()) + ?.projectId, + { strict: false } + ); + return item; }); + res.status(200).send(resultsWithProjectsIds); + }); }); } catch (error) { res.status(400).send(error); @@ -837,14 +898,17 @@ const taskController = function (Task) { const getTasksForTeamsByUser = async (req, res) => { try { const userId = mongoose.Types.ObjectId(req.params.userId); - const teamsData = await taskHelper.getTasksForTeams(userId).exec(); + const teamsData = await taskHelper.getTasksForTeams(userId); if (teamsData.length > 0) { res.status(200).send(teamsData); } else { - const singleUserData = await taskHelper.getTasksForSingleUser(userId).exec(); + const singleUserData = await taskHelper + .getTasksForSingleUser(userId) + .exec(); res.status(200).send(singleUserData); } } catch (error) { + console.log(error); res.status(400).send(error); } }; @@ -854,10 +918,10 @@ const taskController = function (Task) { Task.findOneAndUpdate( { _id: mongoose.Types.ObjectId(taskId) }, - { ...req.body, modifiedDatetime: Date.now() }, + { ...req.body, modifiedDatetime: Date.now() } ) .then(() => res.status(201).send()) - .catch(error => res.status(404).send(error)); + .catch((error) => res.status(404).send(error)); }; const getReviewReqEmailBody = function (name, taskName) { @@ -872,10 +936,12 @@ const taskController = function (Task) { const getRecipients = async function (myUserId) { const recipients = []; - const user = await userProfile.findById(myUserId); - const membership = await userProfile.find({ role: { $in: ['Administrator', 'Manager', 'Mentor'] } }); + const user = await UserProfile.findById(myUserId); + const membership = await UserProfile.find({ + role: { $in: ["Administrator", "Manager", "Mentor"] }, + }); membership.forEach((member) => { - if (member.teams.some(team => user.teams.includes(team))) { + if (member.teams.some((team) => user.teams.includes(team))) { recipients.push(member.email); } }); @@ -883,9 +949,7 @@ const taskController = function (Task) { }; const sendReviewReq = async function (req, res) { - const { - myUserId, name, taskName, - } = req.body; + const { myUserId, name, taskName } = req.body; const emailBody = getReviewReqEmailBody(name, taskName); const recipients = await getRecipients(myUserId); @@ -894,12 +958,12 @@ const taskController = function (Task) { recipients, `Review Request from ${name}`, emailBody, - 'highestgoodnetwork@gmail.com', null, + null ); - res.status(200).send('Success'); + res.status(200).send("Success"); } catch (err) { - res.status(500).send('Failed'); + res.status(500).send("Failed"); } }; @@ -917,7 +981,7 @@ const taskController = function (Task) { updateAllParents, deleteTaskByWBS, moveTask, - getTasksByUserList, + getTasksByUserId, getTasksForTeamsByUser, updateTaskStatus, sendReviewReq, diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js index 5c8cb5cc2..9cd98036e 100644 --- a/src/controllers/teamController.js +++ b/src/controllers/teamController.js @@ -115,66 +115,40 @@ const teamcontroller = function (Team) { return; } - if ( - !req.params.teamId - || !mongoose.Types.ObjectId.isValid(req.params.teamId) - || !req.body.users - || req.body.users.length === 0 - ) { - res.status(400).send({ error: 'Invalid request' }); + const { teamId } = req.params; + + if (!teamId || !mongoose.Types.ObjectId.isValid(teamId)) { + res.status(400).send({ error: 'Invalid teamId' }); return; } // verify team exists + const targetTeam = await Team.findById(teamId); - Team.findById(req.params.teamId) - .then((team) => { - if (!team || team.length === 0) { - res.status(400).send({ error: 'Invalid team' }); - return; - } - const { users } = req.body; - const assignlist = []; - const unassignlist = []; - - users.forEach((element) => { - const { userId, operation } = element; - // if user's profile is stored in cache, clear it so when you visit their profile page it will be up to date - if (cache.hasCache(`user-${userId}`)) cache.removeCache(`user-${userId}`); - - if (operation === 'Assign') { - assignlist.push(userId); - } else { - unassignlist.push(userId); - } - }); + if (!targetTeam || targetTeam.length === 0) { + res.status(400).send({ error: 'Invalid team' }); + return; + } - const addTeamToUserProfile = userProfile - .updateMany({ _id: { $in: assignlist } }, { $addToSet: { teams: team._id } }) - .exec(); - const removeTeamFromUserProfile = userProfile - .updateMany({ _id: { $in: unassignlist } }, { $pull: { teams: team._id } }) - .exec(); - const addUserToTeam = Team.updateOne( - { _id: team._id }, - { $addToSet: { members: { $each: assignlist.map(userId => ({ userId })) } } }, - ).exec(); - const removeUserFromTeam = Team.updateOne( - { _id: team._id }, - { $pull: { members: { userId: { $in: unassignlist } } } }, - ).exec(); - - Promise.all([addTeamToUserProfile, removeTeamFromUserProfile, addUserToTeam, removeUserFromTeam]) - .then(() => { - res.status(200).send({ result: 'Done' }); - }) - .catch((error) => { - res.status(500).send({ error }); - }); - }) - .catch((error) => { - res.status(500).send({ error }); - }); + try { + const { userId, operation } = req.body; + + // if user's profile is stored in cache, clear it so when you visit their profile page it will be up to date + if (cache.hasCache(`user-${userId}`)) cache.removeCache(`user-${userId}`); + + + if (operation === 'Assign') { + await Team.findOneAndUpdate({ _id: teamId }, { $addToSet: { members: { userId } }, $set: { modifiedDatetime: Date.now() } }, { new: true }); + const newMember = await userProfile.findOneAndUpdate({ _id: userId }, { $addToSet: { teams: teamId } }, { new: true }); + res.status(200).send({ newMember }); + } else { + await Team.findOneAndUpdate({ _id: teamId }, { $pull: { members: { userId } }, $set: { modifiedDatetime: Date.now() } }); + await userProfile.findOneAndUpdate({ _id: userId }, { $pull: { teams: teamId } }, { new: true }); + res.status(200).send({ result: 'Delete Success' }); + } + } catch (error) { + res.status(500).send({ error }); + } }; const getTeamMembership = function (req, res) { diff --git a/src/controllers/timeEntryController.js b/src/controllers/timeEntryController.js index effe61a3b..1cbab61c4 100644 --- a/src/controllers/timeEntryController.js +++ b/src/controllers/timeEntryController.js @@ -1,8 +1,9 @@ const moment = require('moment-timezone'); const mongoose = require('mongoose'); const { getInfringementEmailBody } = require('../helpers/userHelper')(); -const userProfile = require('../models/userProfile'); -const task = require('../models/task'); +const UserProfile = require('../models/userProfile'); +const Task = require('../models/task'); +const WBS = require('../models/wbs'); const emailSender = require('../utilities/emailSender'); const { hasPermission } = require('../utilities/permissions'); @@ -59,12 +60,11 @@ const getEditedTimeEntryEmailBody = ( * @param {*} final Final time entry object * @returns {Void} */ -const notifyEditByEmail = async (personId, original, finalTime, final) => { +const notifyEditByEmail = async (personId, originalTime, finalTime, final) => { try { - const originalTime = original.totalSeconds; - const record = await userProfile.findById(personId); + const record = await UserProfile.findById(personId); const requestor = personId !== final.requestor.requestorId - ? await userProfile.findById(final.requestor.requestorId) + ? await UserProfile.findById(final.requestor.requestorId) : record; const emailBody = getEditedTimeEntryEmailBody( record.firstName, @@ -93,7 +93,7 @@ const notifyTaskOvertimeEmailBody = async ( hoursLogged, ) => { try { - const record = await userProfile.findById(personId); + const record = await UserProfile.findById(personId); const text = `Dear ${record.firstName}${record.lastName},
Oops, it looks like you have logged more hours than estimated for a task
Task Name : ${taskName}
@@ -113,12 +113,12 @@ const notifyTaskOvertimeEmailBody = async ( ); } catch (error) { console.log( - `Failed to send email notification about the overtime for a task belonging to user with id ${personId}` + `Failed to send email notification about the overtime for a task belonging to user with id ${personId}`, ); } }; -const checkTaskOvertime = async (timeentry, record, currentTask) => { +const checkTaskOvertime = async (timeentry, currentUser, currentTask) => { try { // send email notification if logged in hours exceeds estiamted hours for a task if (currentTask.hoursLogged > currentTask.estimatedHours) { @@ -131,13 +131,63 @@ const checkTaskOvertime = async (timeentry, record, currentTask) => { } } catch (error) { console.log( - `Failed to find task whose logged-in hours are more than estimated hours ${record.email}` + `Failed to find task whose logged-in hours are more than estimated hours for ${currentUser.email}`, ); } }; +// update timeentry with wbsId and taskId if projectId in the old timeentry is actually a taskId +const updateTaskIdInTimeEntry = async (id, timeEntry) => { + // if id is a taskId, then timeentry should have the parent wbsId and projectId for that task; + // if id is not a taskId, then it is a projectId, timeentry should have both wbsId and taskId to be null; + let taskId = null; + let wbsId = null; + let projectId = id; + const task = await Task.findById(id); + if (task) { + taskId = id; + ({ wbsId } = task); + const wbs = await WBS.findById(wbsId); + ({ projectId } = wbs); + } + Object.assign(timeEntry, { taskId, wbsId, projectId }); +}; + const timeEntrycontroller = function (TimeEntry) { const editTimeEntry = async (req, res) => { + const { timeEntryId } = req.params; + + if (!timeEntryId) { + const error = 'ObjectId in request param is not in correct format'; + return res.status(400).send({ error }); + } + + if (!mongoose.Types.ObjectId.isValid(timeEntryId)) { + const error = 'ObjectIds are not correctly formed'; + return res.status(400).send({ error }); + } + + const { + personId, + hours: newHours = '00', + minutes: newMinutes = '00', + notes: newNotes, + isTangible: newIsTangible, + projectId: newProjectId, + wbsId: newWbsId, + taskId: newTaskId, + dateOfWork: newDateOfWork, + } = req.body; + + const isForAuthUser = personId === req.body.requestor.requestorId; + const isSameDayTimeEntry = moment().tz('America/Los_Angeles').format('YYYY-MM-DD') === newDateOfWork; + const canEdit = (await hasPermission(req.body.requestor, 'editTimeEntry')) || (isForAuthUser && isSameDayTimeEntry); + + if (!canEdit) { + const error = 'Unauthorized request'; + return res.status(403).send({ error }); + } + const session = await mongoose.startSession(); session.startTransaction(); @@ -145,112 +195,85 @@ const timeEntrycontroller = function (TimeEntry) { const isGeneralEntry = isGeneralTimeEntry(type); try { - if (!req.params.timeEntryId) { - return res - .status(400) - .send({ - error: 'ObjectId in request param is not in correct format', - }); + if (!timeEntryId) { + const error = 'ObjectId in request param is not in correct format'; + return res.status(400).send({ error }); } if ( - !mongoose.Types.ObjectId.isValid(req.params.timeEntryId) + !mongoose.Types.ObjectId.isValid(timeEntryId) || ((isGeneralEntry || type === 'project') - && !mongoose.Types.ObjectId.isValid(req.body.projectId) + && !mongoose.Types.ObjectId.isValid(newProjectId) )) { - return res - .status(400) - .send({ error: 'ObjectIds are not correctly formed' }); + const error = 'ObjectIds are not correctly formed'; + return res.status(400).send({ error }); } // Get initial timeEntry by timeEntryId - const timeEntry = await TimeEntry.findById(req.params.timeEntryId); - + const timeEntry = await TimeEntry.findById(timeEntryId); if (!timeEntry) { - return res - .status(400) - .send({ - error: `No valid records found for ${req.params.timeEntryId}`, - }); - } - - if ( - !( - (await hasPermission(req.body.requestor, 'editTimeEntry')) - || (isGeneralEntry - && timeEntry.personId.toString() - === req.body.requestor.requestorId.toString() - ) - )) { - return res.status(403).send({ error: 'Unauthorized request' }); + const error = `No valid records found for ${timeEntryId}`; + return res.status(400).send({ error }); } - const hours = req.body.hours ? req.body.hours : '00'; - const minutes = req.body.minutes ? req.body.minutes : '00'; + const newTotalSeconds = moment.duration({ hours: newHours, minutes: newMinutes }).asSeconds(); - const totalSeconds = moment.duration(`${hours}:${minutes}`).asSeconds(); - - if ( - isGeneralEntry - && timeEntry.isTangible === true - && totalSeconds !== timeEntry.totalSeconds - ) { + if (isGeneralEntry && timeEntry.isTangible && newIsTangible && newTotalSeconds !== timeEntry.totalSeconds) { notifyEditByEmail( timeEntry.personId.toString(), - timeEntry, - totalSeconds, + timeEntry.totalSeconds, + newTotalSeconds, req.body, ); } - const initialSeconds = timeEntry.totalSeconds; - const initialProjectId = timeEntry.projectId; - const initialIsTangible = timeEntry.isTangible; - // Get the task related to this time entry, if not found, then it's a project and will be null - const findTask = await task.findById(initialProjectId); - - timeEntry.notes = req.body.notes; - timeEntry.totalSeconds = totalSeconds; - timeEntry.isTangible = req.body.isTangible; - timeEntry.lastModifiedDateTime = moment().utc().toISOString(); - timeEntry.projectId = mongoose.Types.ObjectId(req.body.projectId); - timeEntry.dateOfWork = moment(req.body.dateOfWork).format('YYYY-MM-DD'); - timeEntry.entryType = req.body.entryType === undefined ? 'default' : req.body.entryType; - - // Update the hoursLogged field of related tasks based on before and after timeEntries - // initialIsTangible is a bealoon value, req.body.isTangible is a string - // initialProjectId may be a task id or project id, so do not throw error. - try { - if (isGeneralEntry && findTask) { - if (initialIsTangible === true) { - findTask.hoursLogged -= initialSeconds / 3600; + // update task data if project/task is changed + if (newTaskId === timeEntry.taskId && newProjectId === timeEntry.projectId) { + // when project/task is the same + const timeEntryTask = await Task.findById(newTaskId); + if (timeEntryTask) { + const timeEntryUser = await UserProfile.findById(personId); + if (timeEntry.isTangible) { + timeEntryTask.hoursLogged -= timeEntry.totalSeconds / 3600; } - - if (req.body.isTangible === true) { - findTask.hoursLogged += totalSeconds / 3600; + if (newIsTangible) { + timeEntryTask.hoursLogged += newTotalSeconds / 3600; } - - await findTask.save(); + checkTaskOvertime(timeEntry, timeEntryUser, timeEntryTask); + await timeEntryTask.save(); + } + } else { + // update oldtTimeEntryTask + const oldTimeEntryTask = await Task.findById(timeEntry.taskId); + if (oldTimeEntryTask && timeEntry.isTangible) { + oldTimeEntryTask.hoursLogged -= timeEntry.totalSeconds / 3600; + oldTimeEntryTask.save(); + } + // update newtTimeEntryTask + const newTimeEntryTask = await Task.findById(newTaskId); + if (newTimeEntryTask && newIsTangible) { + const timeEntryUser = await UserProfile.findById(personId); + newTimeEntryTask.hoursLogged += newTotalSeconds / 3600; + checkTaskOvertime(timeEntry, timeEntryUser, newTimeEntryTask); + await newTimeEntryTask.save(); } - } catch (error) { - throw new Error(error); } // Update edit history - if ( - (isGeneralEntry || type === 'person') - && initialSeconds !== totalSeconds + if ((isGeneralEntry || type === 'person') + && timeEntry.totalSeconds !== newTotalSeconds && timeEntry.isTangible - && req.body.requestor.requestorId === timeEntry.personId.toString() + && isForAuthUser && !(await hasPermission(req.body.requestor, 'editTimeEntry')) ) { - const requestor = await userProfile.findById( + const requestor = await UserProfile.findById( req.body.requestor.requestorId, ); + requestor.timeEntryEditHistory.push({ date: moment().tz('America/Los_Angeles').toDate(), - initialSeconds, - newSeconds: totalSeconds, + initialSeconds: timeEntry.totalSeconds, + newSeconds: newTotalSeconds, }); if (isGeneralEntry) { @@ -259,10 +282,10 @@ const timeEntrycontroller = function (TimeEntry) { requestor.timeEntryEditHistory.forEach((edit) => { if ( - moment().tz('America/Los_Angeles').diff(edit.date, 'days') <= 365 - ) { - totalRecentEdits += 1; - } + moment() + .tz('America/Los_Angeles') + .diff(edit.date, 'days') <= 365 + ) totalRecentEdits += 1; }); if (totalRecentEdits >= 5) { @@ -302,31 +325,26 @@ const timeEntrycontroller = function (TimeEntry) { ); } } - await requestor.save(); } + timeEntry.notes = newNotes; + timeEntry.totalSeconds = newTotalSeconds; + timeEntry.isTangible = newIsTangible; + timeEntry.lastModifiedDateTime = moment().utc().toISOString(); + timeEntry.projectId = mongoose.Types.ObjectId(newProjectId); + timeEntry.wbsId = newWbsId ? mongoose.Types.ObjectId(newWbsId) : null; + timeEntry.taskId = newTaskId ? mongoose.Types.ObjectId(newTaskId) : null; + timeEntry.dateOfWork = moment(newDateOfWork).format('YYYY-MM-DD'); await timeEntry.save(); - res.status(200).send({ message: 'Successfully updated time entry' }); - - // If the time entry isn't related to a task (i.e. it's a project), then don't check for overtime (Most likely pr team) - if (isGeneralEntry && findTask) { - // checking if logged in hours exceed estimated time after timeentry edit for a task - const record = await userProfile.findById( - timeEntry.personId.toString(), - ); - const currentTask = await task.findById(req.body.projectId); - checkTaskOvertime(timeEntry, record, currentTask); - } + return res.status(200).send({ message: 'Successfully updated time entry' }); } catch (err) { await session.abortTransaction(); return res.status(400).send({ error: err.toString() }); } finally { session.endSession(); } - - return res.status(200).send(); }; const getAllTimeEnteries = function (req, res) { @@ -341,6 +359,8 @@ const timeEntrycontroller = function (TimeEntry) { const timeentry = new TimeEntry(); timeentry.personId = element.personId; timeentry.projectId = element.projectId; + timeentry.wbsId = element.wbsId; + timeentry.taskId = element.taskId; timeentry.dateOfWork = element.dateOfWork; timeentry.timeSpent = moment('1900-01-01 00:00:00') .add(element.totalSeconds, 'seconds') @@ -358,8 +378,7 @@ const timeEntrycontroller = function (TimeEntry) { const postTimeEntry = async function (req, res) { const isInvalid = !req.body.dateOfWork || !moment(req.body.dateOfWork).isValid() - || !req.body.timeSpent - || !req.body.isTangible; + || !req.body.timeSpent; const returnErr = (result) => { result.status(400).send({ error: 'Bad request' }); @@ -398,59 +417,44 @@ const timeEntrycontroller = function (TimeEntry) { break; } - const timeentry = new TimeEntry(); + const timeEntry = new TimeEntry(); const { dateOfWork, timeSpent } = req.body; - timeentry.personId = req.body.personId; - timeentry.projectId = req.body.projectId; - timeentry.teamId = req.body.teamId; - timeentry.dateOfWork = moment(dateOfWork).format('YYYY-MM-DD'); - timeentry.totalSeconds = moment.duration(timeSpent).asSeconds(); - timeentry.notes = req.body.notes; - timeentry.isTangible = req.body.isTangible; - timeentry.createdDateTime = moment().utc().toISOString(); - timeentry.lastModifiedDateTime = moment().utc().toISOString(); - timeentry.entryType = req.body.entryType === undefined ? 'default' : req.body.entryType; - - timeentry - .save() - .then((results) => { - res - .status(200) - .send({ message: `Time Entry saved with id as ${results._id}` }); - }) - .catch((error) => { - res.status(400).send(error); - }); - - if (timeentry.entryType === 'default') { - // Get the task related to this time entry, if not found, then it's a project sets to null - const currentTask = await task - .findById(req.body.projectId) - .catch(() => null); - - // Add this tangbile time entry to related task's hoursLogged and checks if timeEntry is related to a task - if (timeentry.isTangible === true && currentTask) { - try { - currentTask.hoursLogged += timeentry.totalSeconds / 3600; - await currentTask.save(); - } catch (error) { - throw new Error(error); - } + timeEntry.personId = req.body.personId; + timeEntry.projectId = req.body.projectId; + timeEntry.wbsId = req.body.wbsId; + timeEntry.taskId = req.body.taskId; + timeEntry.teamId = req.body.teamId; + timeEntry.dateOfWork = moment(dateOfWork).format('YYYY-MM-DD'); + timeEntry.totalSeconds = moment.duration(timeSpent).asSeconds(); + timeEntry.notes = req.body.notes; + timeEntry.isTangible = req.body.isTangible; + timeEntry.createdDateTime = moment().utc().toISOString(); + timeEntry.lastModifiedDateTime = moment().utc().toISOString(); + timeEntry.entryType = req.body.entryType; + + if (timeEntry.taskId) { + const timeEntryTask = await Task.findById(timeEntry.taskId); + const timeEntryUser = await UserProfile.findById(timeEntry.personId); + if (timeEntry.isTangible) { + timeEntryTask.hoursLogged += timeEntry.totalSeconds / 3600; } + checkTaskOvertime(timeEntry, timeEntryUser, timeEntryTask); + await timeEntryTask.save(); + } - // checking if logged in hours exceed estimated time after timeentry for a task, only if the time entry is related to a task (It might not be, if it's a project) - if (currentTask) { - try { - const record = await userProfile.findById(timeentry.personId.toString()); - checkTaskOvertime(timeentry, record, currentTask); - } catch (error) { - throw new Error(error); - } - } + try { + return timeEntry + .save() + .then(results => res.status(200).send({ + message: `Time Entry saved with id as ${results._id}`, + })) + .catch(error => res.status(400).send(error)); + } catch (error) { + return res.status(500).send(error); } }; - const getTimeEntriesForSpecifiedPeriod = function (req, res) { + const getTimeEntriesForSpecifiedPeriod = async function (req, res) { if ( !req.params || !req.params.fromdate @@ -471,75 +475,27 @@ const timeEntrycontroller = function (TimeEntry) { .format('YYYY-MM-DD'); const { userId } = req.params; - TimeEntry.aggregate([ - { - $match: { - entryType: { $in: ['default', null] }, - personId: mongoose.Types.ObjectId(userId), - dateOfWork: { $gte: fromdate, $lte: todate }, - }, - }, - { - $lookup: { - from: 'projects', - localField: 'projectId', - foreignField: '_id', - as: 'project', - }, - }, - { - $lookup: { - from: 'tasks', - localField: 'projectId', - foreignField: '_id', - as: 'task', - }, - }, - { - $project: { - _id: 1, - notes: 1, - isTangible: 1, - personId: 1, - projectId: 1, - lastModifiedDateTime: 1, - projectName: { - $arrayElemAt: ['$project.projectName', 0], - }, - taskName: { - $arrayElemAt: ['$task.taskName', 0], - }, - category: { - $arrayElemAt: ['$project.category', 0], - }, - classification: { - $arrayElemAt: ['$task.classification', 0], - }, - dateOfWork: 1, - hours: { - $floor: { - $divide: ['$totalSeconds', 3600], - }, - }, - minutes: { - $floor: { - $divide: [{ $mod: ['$totalSeconds', 3600] }, 60], - }, - }, - }, - }, - { - $sort: { - lastModifiedDateTime: -1, - }, - }, - ]) - .then((results) => { - res.status(200).send(results); - }) - .catch((error) => { - res.status(400).send(error); - }); + try { + const timeEntries = await TimeEntry.find({ + entryType: { $in: ['default', null] }, + personId: userId, + dateOfWork: { $gte: fromdate, $lte: todate }, + }).sort('-lastModifiedDateTime'); + + const results = await Promise.all(timeEntries.map(async (timeEntry) => { + timeEntry = { ...timeEntry.toObject() }; + const { projectId, taskId } = timeEntry; + if (!taskId) await updateTaskIdInTimeEntry(projectId, timeEntry); // if no taskId, then it might be old time entry data that didn't separate projectId with taskId + const hours = Math.floor(timeEntry.totalSeconds / 3600); + const minutes = Math.floor((timeEntry.totalSeconds % 3600) / 60); + Object.assign(timeEntry, { hours, minutes, totalSeconds: undefined }); + return timeEntry; + })); + + res.status(200).send(results); + } catch (error) { + res.status(400).send({ error }); + } }; const getTimeEntriesForUsersList = function (req, res) { @@ -641,7 +597,7 @@ const timeEntrycontroller = function (TimeEntry) { ) { // Revert this tangible timeEntry of related task's hoursLogged if (record.isTangible === true) { - task + Task .findById(record.projectId) .then((currentTask) => { // If the time entry isn't related to a task (i.e. it's a project), then don't revert hours (Most likely pr team) diff --git a/src/controllers/wbsController.js b/src/controllers/wbsController.js index fa7f4427f..04070eaf9 100644 --- a/src/controllers/wbsController.js +++ b/src/controllers/wbsController.js @@ -1,4 +1,7 @@ +const mongoose = require('mongoose'); const { hasPermission } = require('../utilities/permissions'); +const Project = require('../models/project'); +const Task = require('../models/task'); const wbsController = function (WBS) { const getAllWBS = function (req, res) { @@ -68,12 +71,32 @@ const wbsController = function (WBS) { .catch(error => res.status(404).send(error)); }; + const getWBSByUserId = async function (req, res) { + const { userId } = req.params; + try { + const result = await Task.aggregate() + .match({ 'resources.userID': mongoose.Types.ObjectId(userId) }) + .project('wbsId -_id') + .group({ _id: '$wbsId' }) + .lookup({ + from: 'wbs', localField: '_id', foreignField: '_id', as: 'wbs', + }) + .unwind('wbs') + .replaceRoot('wbs'); + + res.status(200).send(result); + } catch (error) { + res.status(404).send(error); + } + }; + return { postWBS, deleteWBS, getAllWBS, getWBS, getWBSById, + getWBSByUserId, }; }; diff --git a/src/cronjobs/userProfileJobs.js b/src/cronjobs/userProfileJobs.js index f3903c057..8bc94c0f1 100644 --- a/src/cronjobs/userProfileJobs.js +++ b/src/cronjobs/userProfileJobs.js @@ -5,7 +5,8 @@ const userhelper = require('../helpers/userHelper')(); const userProfileJobs = () => { const allUserProfileJobs = new CronJob( - '1 0 * * *', // Every day, 1 minute past midnight (PST). + // '* * * * *', // Comment out for testing. Run Every minute. + '1 0 * * 0', // Every Sunday, 1 minute past midnight. async () => { const SUNDAY = 0; if (moment().tz('America/Los_Angeles').day() === SUNDAY) { diff --git a/src/helpers/dashboardhelper.js b/src/helpers/dashboardhelper.js index b7ca2f131..929b5781b 100644 --- a/src/helpers/dashboardhelper.js +++ b/src/helpers/dashboardhelper.js @@ -3,6 +3,8 @@ const mongoose = require('mongoose'); const userProfile = require('../models/userProfile'); const timeentry = require('../models/timeentry'); const myTeam = require('../helpers/helperModels/myTeam'); +const team = require('../models/team'); + const dashboardhelper = function () { const personaldetails = function (userId) { @@ -161,6 +163,11 @@ const dashboardhelper = function () { const getLeaderboard = async function (userId) { const userid = mongoose.Types.ObjectId(userId); + const userById = await userProfile.findOne({ _id: userid , isActive:true}, {role:1}) + .then((res)=>{ return res; }).catch((e)=>{}); + + if(userById==null) return null; + const userRole = userById.role; const pdtstart = moment() .tz('America/Los_Angeles') .startOf('week') @@ -169,262 +176,358 @@ const dashboardhelper = function () { .tz('America/Los_Angeles') .endOf('week') .format('YYYY-MM-DD'); - const output = await myTeam.aggregate([ - { - $match: { - _id: userid, - }, - }, - { - $unwind: '$myteam', - }, - { - $project: { - _id: 0, - role: 1, - personId: '$myteam._id', - name: '$myteam.fullName', - }, - }, - { - $lookup: { - from: 'userProfiles', - localField: 'personId', - foreignField: '_id', - as: 'persondata', - }, - }, - { - $match: { - // leaderboard user roles hierarchy - $or: [ - { - role: { $in: ['Owner', 'Core Team'] }, - }, - { - $and: [ - { - role: 'Administrator', - }, - { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, - ], - }, - { - $and: [ - { - role: { $in: ['Manager', 'Mentor'] }, - }, - { - 'persondata.0.role': { - $nin: ['Manager', 'Mentor', 'Core Team', 'Administrator', 'Owner'], - }, - }, - ], - }, - { 'persondata.0._id': userId }, - { 'persondata.0.role': 'Volunteer' }, - { 'persondata.0.isVisible': true }, - ], - }, - }, - { - $project: { - personId: 1, - name: 1, - role: { - $arrayElemAt: ['$persondata.role', 0], - }, - isVisible: { - $arrayElemAt: ['$persondata.isVisible', 0], - }, - hasSummary: { - $ne: [ - { - $arrayElemAt: [ - { - $arrayElemAt: ['$persondata.weeklySummaries.summary', 0], - }, - 0, - ], - }, - '', - ], - }, - weeklycommittedHours: { - $sum: [ - { - $arrayElemAt: ['$persondata.weeklycommittedHours', 0], - }, - { - $ifNull: [{ $arrayElemAt: ['$persondata.missedHours', 0] }, 0], - }, - ], - }, - }, - }, - { - $lookup: { - from: 'timeEntries', - localField: 'personId', - foreignField: 'personId', - as: 'timeEntryData', - }, - }, - { - $project: { - personId: 1, - name: 1, - role: 1, - isVisible: 1, - hasSummary: 1, - weeklycommittedHours: 1, - timeEntryData: { - $filter: { - input: '$timeEntryData', - as: 'timeentry', - cond: { - $and: [ - { - $gte: ['$$timeentry.dateOfWork', pdtstart], - }, - { - $lte: ['$$timeentry.dateOfWork', pdtend], - }, - { - $not: [ - { - $in: ['$$timeentry.entryType', ['person', 'team', 'project']], - }, - ], - }, - ], - }, - }, - }, - }, - }, - { - $unwind: { - path: '$timeEntryData', - preserveNullAndEmptyArrays: true, - }, - }, - { - $project: { - personId: 1, - name: 1, - role: 1, - isVisible: 1, - hasSummary: 1, - weeklycommittedHours: 1, - totalSeconds: { - $cond: [ - { - $gte: ['$timeEntryData.totalSeconds', 0], - }, - '$timeEntryData.totalSeconds', - 0, - ], - }, - isTangible: { - $cond: [ - { - $gte: ['$timeEntryData.totalSeconds', 0], - }, - '$timeEntryData.isTangible', - false, - ], - }, - }, - }, - { - $addFields: { - tangibletime: { - $cond: [ - { - $eq: ['$isTangible', true], - }, - '$totalSeconds', - 0, - ], - }, - intangibletime: { - $cond: [ - { - $eq: ['$isTangible', false], - }, - '$totalSeconds', - 0, - ], - }, - }, - }, - { - $group: { - _id: { - personId: '$personId', - weeklycommittedHours: '$weeklycommittedHours', - name: '$name', - role: '$role', - isVisible: '$isVisible', - hasSummary: '$hasSummary', - }, - totalSeconds: { - $sum: '$totalSeconds', - }, - tangibletime: { - $sum: '$tangibletime', - }, - intangibletime: { - $sum: '$intangibletime', - }, - }, - }, - { - $project: { - _id: 0, - personId: '$_id.personId', - name: '$_id.name', - role: '$_id.role', - isVisible: '$_id.isVisible', - hasSummary: '$_id.hasSummary', - weeklycommittedHours: '$_id.weeklycommittedHours', - totaltime_hrs: { - $divide: ['$totalSeconds', 3600], - }, - totaltangibletime_hrs: { - $divide: ['$tangibletime', 3600], - }, - totalintangibletime_hrs: { - $divide: ['$intangibletime', 3600], - }, - percentagespentintangible: { - $cond: [ - { - $eq: ['$totalSeconds', 0], - }, - 0, - { - $multiply: [ - { - $divide: ['$tangibletime', '$totalSeconds'], - }, - 100, - ], - }, - ], - }, - }, - }, - { - $sort: { - totaltangibletime_hrs: -1, - name: 1, - role: 1, - }, + + let teamMemberIds = [userid] + let teamMembers = []; + + if(userRole!='Administrator' && userRole!='Owner' && userRole!='Core Team') //Manager , Mentor , Volunteer ... , Show only team members + { + + const teamsResult = await team.find( { "members.userId": { $in: [userid] } }, {members:1} ) + .then((res)=>{ return res; }).catch((e)=>{}) + + teamsResult.map((_myTeam)=>{ + _myTeam.members.map((teamMember)=> { + if(!teamMember.userId.equals(userid)) + teamMemberIds.push( teamMember.userId ); + } ) + }) + + teamMembers = await userProfile.find({ _id: { $in: teamMemberIds } , isActive:true }, + {role:1,firstName:1,lastName:1,isVisible:1,weeklycommittedHours:1,weeklySummaries:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + + } + else { + if(userRole == 'Administrator'){ //All users except Owner and Core Team + const excludedRoles = ['Core Team', 'Owner']; + teamMembers = await userProfile.find({ isActive:true , role: { $nin: excludedRoles } }, + {role:1,firstName:1,lastName:1,isVisible:1,weeklycommittedHours:1,weeklySummaries:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + } + else{ //'Core Team', 'Owner' //All users + teamMembers = await userProfile.find({ isActive:true}, + {role:1,firstName:1,lastName:1,isVisible:1,weeklycommittedHours:1,weeklySummaries:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + } + + + } + + teamMemberIds = teamMembers.map(member => member._id); + + const timeEntries = await timeentry.find({ + dateOfWork: { + $gte: pdtstart, + $lte: pdtend, }, - ]); - return output; + personId: { $in: teamMemberIds } + }); + + let timeEntryByPerson = {} + timeEntries.map((timeEntry)=>{ + + let personIdStr = timeEntry.personId.toString(); + + if(timeEntryByPerson[personIdStr]==null) + timeEntryByPerson[personIdStr] = {tangibleSeconds:0,intangibleSeconds:0,totalSeconds:0}; + + if (timeEntry.isTangible === true) { + timeEntryByPerson[personIdStr].tangibleSeconds += timeEntry.totalSeconds; + } else { + timeEntryByPerson[personIdStr].intangibleSeconds += timeEntry.totalSeconds; + } + + timeEntryByPerson[personIdStr].totalSeconds += timeEntry.totalSeconds; + }) + + + let leaderBoardData = []; + teamMembers.map((teamMember)=>{ + let obj = { + personId : teamMember._id, + role : teamMember.role, + name : teamMember.firstName + ' ' + teamMember.lastName, + isVisible : teamMember.isVisible, + hasSummary : teamMember.weeklySummaries?.length > 0 ? teamMember.weeklySummaries[0].summary!='' : false, + weeklycommittedHours : teamMember.weeklycommittedHours, + totaltangibletime_hrs : ((timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds / 3600) || 0), + totalintangibletime_hrs : ((timeEntryByPerson[teamMember._id.toString()]?.intangibleSeconds / 3600) || 0), + totaltime_hrs : ((timeEntryByPerson[teamMember._id.toString()]?.totalSeconds / 3600) || 0), + percentagespentintangible : + (timeEntryByPerson[teamMember._id.toString()] && timeEntryByPerson[teamMember._id.toString()]?.totalSeconds !=0 && timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds !=0) ? + (timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds / timeEntryByPerson[teamMember._id.toString()]?.totalSeconds) * 100 + : + 0 + } + leaderBoardData.push(obj); + }) + + let sortedLBData = leaderBoardData.sort((a, b) => { + // Sort by totaltangibletime_hrs in descending order + if (b.totaltangibletime_hrs !== a.totaltangibletime_hrs) { + return b.totaltangibletime_hrs - a.totaltangibletime_hrs; + } + + // Then sort by name in ascending order + if (a.name !== b.name) { + return a.name.localeCompare(b.name); + } + + // Finally, sort by role in ascending order + return a.role.localeCompare(b.role); + }); + + return sortedLBData; + + // return myTeam.aggregate([ + // { + // $match: { + // _id: userid, + // }, + // }, + // { + // $unwind: '$myteam', + // }, + // { + // $project: { + // _id: 0, + // role: 1, + // personId: '$myteam._id', + // name: '$myteam.fullName', + // }, + // }, + // { + // $lookup: { + // from: 'userProfiles', + // localField: 'personId', + // foreignField: '_id', + // as: 'persondata', + // }, + // }, + // { + // $match: { + // // leaderboard user roles hierarchy + // $or: [ + // { + // role: { $in: ['Owner', 'Core Team'] }, + // }, + // { + // $and: [ + // { + // role: 'Administrator', + // }, + // { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, + // ], + // }, + // { + // $and: [ + // { + // role: { $in: ['Manager', 'Mentor'] }, + // }, + // { + // 'persondata.0.role': { + // $nin: ['Manager', 'Mentor', 'Core Team', 'Administrator', 'Owner'], + // }, + // }, + // ], + // }, + // { 'persondata.0._id': userId }, + // { 'persondata.0.role': 'Volunteer' }, + // { 'persondata.0.isVisible': true }, + // ], + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // role: { + // $arrayElemAt: ['$persondata.role', 0], + // }, + // isVisible: { + // $arrayElemAt: ['$persondata.isVisible', 0], + // }, + // hasSummary: { + // $ne: [ + // { + // $arrayElemAt: [ + // { + // $arrayElemAt: ['$persondata.weeklySummaries.summary', 0], + // }, + // 0, + // ], + // }, + // '', + // ], + // }, + // weeklycommittedHours: { + // $sum: [ + // { + // $arrayElemAt: ['$persondata.weeklycommittedHours', 0], + // }, + // { + // $ifNull: [{ $arrayElemAt: ['$persondata.missedHours', 0] }, 0], + // }, + // ], + // }, + // }, + // }, + // { + // $lookup: { + // from: 'timeEntries', + // localField: 'personId', + // foreignField: 'personId', + // as: 'timeEntryData', + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // role: 1, + // isVisible: 1, + // hasSummary: 1, + // weeklycommittedHours: 1, + // timeEntryData: { + // $filter: { + // input: '$timeEntryData', + // as: 'timeentry', + // cond: { + // $and: [ + // { + // $gte: ['$$timeentry.dateOfWork', pdtstart], + // }, + // { + // $lte: ['$$timeentry.dateOfWork', pdtend], + // }, + // ], + // }, + // }, + // }, + // }, + // }, + // { + // $unwind: { + // path: '$timeEntryData', + // preserveNullAndEmptyArrays: true, + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // role: 1, + // isVisible: 1, + // hasSummary: 1, + // weeklycommittedHours: 1, + // totalSeconds: { + // $cond: [ + // { + // $gte: ['$timeEntryData.totalSeconds', 0], + // }, + // '$timeEntryData.totalSeconds', + // 0, + // ], + // }, + // isTangible: { + // $cond: [ + // { + // $gte: ['$timeEntryData.totalSeconds', 0], + // }, + // '$timeEntryData.isTangible', + // false, + // ], + // }, + // }, + // }, + // { + // $addFields: { + // tangibletime: { + // $cond: [ + // { + // $eq: ['$isTangible', true], + // }, + // '$totalSeconds', + // 0, + // ], + // }, + // intangibletime: { + // $cond: [ + // { + // $eq: ['$isTangible', false], + // }, + // '$totalSeconds', + // 0, + // ], + // }, + // }, + // }, + // { + // $group: { + // _id: { + // personId: '$personId', + // weeklycommittedHours: '$weeklycommittedHours', + // name: '$name', + // role: '$role', + // isVisible: '$isVisible', + // hasSummary: '$hasSummary', + // }, + // totalSeconds: { + // $sum: '$totalSeconds', + // }, + // tangibletime: { + // $sum: '$tangibletime', + // }, + // intangibletime: { + // $sum: '$intangibletime', + // }, + // }, + // }, + // { + // $project: { + // _id: 0, + // personId: '$_id.personId', + // name: '$_id.name', + // role: '$_id.role', + // isVisible: '$_id.isVisible', + // hasSummary: '$_id.hasSummary', + // weeklycommittedHours: '$_id.weeklycommittedHours', + // totaltime_hrs: { + // $divide: ['$totalSeconds', 3600], + // }, + // totaltangibletime_hrs: { + // $divide: ['$tangibletime', 3600], + // }, + // totalintangibletime_hrs: { + // $divide: ['$intangibletime', 3600], + // }, + // percentagespentintangible: { + // $cond: [ + // { + // $eq: ['$totalSeconds', 0], + // }, + // 0, + // { + // $multiply: [ + // { + // $divide: ['$tangibletime', '$totalSeconds'], + // }, + // 100, + // ], + // }, + // ], + // }, + // }, + // }, + // { + // $sort: { + // totaltangibletime_hrs: -1, + // name: 1, + // role: 1, + // }, + // }, + // ]); }; /** diff --git a/src/helpers/helperModels/userProjects.js b/src/helpers/helperModels/userProjects.js index 108ec345b..e325a0e39 100644 --- a/src/helpers/helperModels/userProjects.js +++ b/src/helpers/helperModels/userProjects.js @@ -5,6 +5,7 @@ const { Schema } = mongoose; const ProjectSchema = new Schema({ projectId: { type: mongoose.SchemaTypes.ObjectId, ref: 'projects' }, projectName: { type: String }, + category: { type: String }, }); diff --git a/src/helpers/taskHelper.js b/src/helpers/taskHelper.js index cfb1235fd..8c45df737 100644 --- a/src/helpers/taskHelper.js +++ b/src/helpers/taskHelper.js @@ -1,9 +1,22 @@ const moment = require('moment-timezone'); const userProfile = require('../models/userProfile'); -const myteam = require('../helpers/helperModels/myTeam'); +const timeentry = require('../models/timeentry'); +const myTeam = require('../helpers/helperModels/myTeam'); +const team = require('../models/team'); +const Task = require('../models/task'); +const TaskNotification = require('../models/taskNotification'); +const Wbs = require('../models/wbs'); +const mongoose = require('mongoose'); const taskHelper = function () { - const getTasksForTeams = function (userId) { + const getTasksForTeams = async function (userId) { + const userid = mongoose.Types.ObjectId(userId); + const userById = await userProfile.findOne({ _id: userid , isActive:true}, {role:1,firstName:1, lastName:1, role:1, isVisible:1, weeklycommittedHours:1, weeklySummaries:1}) + .then((res)=>{ return res; }).catch((e)=>{}); + + if(userById==null) return null; + const userRole = userById.role; + const pdtstart = moment() .tz('America/Los_Angeles') .startOf('week') @@ -12,291 +25,413 @@ const taskHelper = function () { .tz('America/Los_Angeles') .endOf('week') .format('YYYY-MM-DD'); - return myteam.aggregate([ - { - $match: { - _id: userId, - }, - }, - { - $unwind: '$myteam', - }, - { - $project: { - _id: 0, - personId: '$myteam._id', - name: '$myteam.fullName', - role: 1, - }, - }, - // have personId, name, role - { - $lookup: { - from: 'userProfiles', - localField: 'personId', - foreignField: '_id', - as: 'persondata', - }, - }, - { - $match: { - // dashboard tasks user roles hierarchy - $or: [ - { - role: { $in: ['Owner', 'Core Team'] }, - }, - { - $and: [ - { - role: 'Administrator', - }, - { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, - ], - }, - { - $and: [ - { - role: { $in: ['Manager', 'Mentor'] }, - }, - { - 'persondata.0.role': { - $nin: ['Manager', 'Mentor', 'Core Team', 'Administrator', 'Owner'], - }, - }, - ], - }, - { 'persondata.0._id': userId }, - { 'persondata.0.role': 'Volunteer' }, - { 'persondata.0.isVisible': true }, - ], - }, - }, - { - $project: { - personId: 1, - name: 1, - weeklycommittedHours: { - $sum: [ - { - $arrayElemAt: ['$persondata.weeklycommittedHours', 0], - }, - { - $ifNull: [{ $arrayElemAt: ['$persondata.missedHours', 0] }, 0], - }, - ], - }, - role: 1, - }, - }, - { - $lookup: { - from: 'timeEntries', - localField: 'personId', - foreignField: 'personId', - as: 'timeEntryData', - }, - }, - { - $project: { - personId: 1, - name: 1, - weeklycommittedHours: 1, - timeEntryData: { - $filter: { - input: '$timeEntryData', - as: 'timeentry', - cond: { - $and: [ - { - $gte: ['$$timeentry.dateOfWork', pdtstart], - }, - { - $lte: ['$$timeentry.dateOfWork', pdtend], - }, - { - $in: ['$$timeentry.entryType', ['default', null]], - }, - ], - }, - }, - }, - role: 1, - }, - }, - { - $unwind: { - path: '$timeEntryData', - preserveNullAndEmptyArrays: true, - }, - }, - { - $project: { - personId: 1, - name: 1, - weeklycommittedHours: 1, - totalSeconds: { - $cond: [ - { - $gte: ['$timeEntryData.totalSeconds', 0], - }, - '$timeEntryData.totalSeconds', - 0, - ], - }, - isTangible: { - $cond: [ - { - $gte: ['$timeEntryData.totalSeconds', 0], - }, - '$timeEntryData.isTangible', - false, - ], - }, - role: 1, - }, - }, - { - $addFields: { - tangibletime: { - $cond: [ - { - $eq: ['$isTangible', true], - }, - '$totalSeconds', - 0, - ], - }, - }, - }, - { - $group: { - _id: { - personId: '$personId', - weeklycommittedHours: '$weeklycommittedHours', - name: '$name', - role: '$role', - }, - totalSeconds: { - $sum: '$totalSeconds', - }, - tangibletime: { - $sum: '$tangibletime', - }, - }, - }, - { - $project: { - _id: 0, - personId: '$_id.personId', - name: '$_id.name', - weeklycommittedHours: '$_id.weeklycommittedHours', - totaltime_hrs: { - $divide: ['$totalSeconds', 3600], - }, - totaltangibletime_hrs: { - $divide: ['$tangibletime', 3600], - }, - role: '$_id.role', - }, - }, - { - $lookup: { - from: 'tasks', - localField: 'personId', - foreignField: 'resources.userID', - as: 'tasks', - }, - }, - { - $project: { - tasks: { - resources: { - profilePic: 0, - }, - }, - }, - }, - { - $unwind: { - path: '$tasks', - preserveNullAndEmptyArrays: true, - }, - }, - { - $lookup: { - from: 'wbs', - localField: 'tasks.wbsId', - foreignField: '_id', - as: 'projectId', - }, - }, - { - $addFields: { - 'tasks.projectId': { - $cond: [ - { $ne: ['$projectId', []] }, - { $arrayElemAt: ['$projectId', 0] }, - '$tasks.projectId', - ], - }, - }, - }, - { - $project: { - projectId: 0, - tasks: { - projectId: { - _id: 0, - isActive: 0, - modifiedDatetime: 0, - wbsName: 0, - createdDatetime: 0, - __v: 0, - }, - }, - }, - }, - { - $addFields: { - 'tasks.projectId': '$tasks.projectId.projectId', - }, - }, - { - $lookup: { - from: 'taskNotifications', - localField: 'tasks._id', - foreignField: 'taskId', - as: 'tasks.taskNotifications', - }, - }, - { - $group: { - _id: '$personId', - tasks: { - $push: '$tasks', - }, - data: { - $first: '$$ROOT', - }, - }, - }, - { - $addFields: { - 'data.tasks': { - $filter: { - input: '$tasks', - as: 'task', - cond: { $ne: ['$$task', {}] }, - }, - }, - }, - }, + + let teamMemberIds = [userid] + let teamMembers = []; + + if(userRole!='Administrator' && userRole!='Owner' && userRole!='Core Team') //Manager , Mentor , Volunteer ... , Show only team members { - $replaceRoot: { - newRoot: '$data', - }, - }, - ]); + + const teamsResult = await team.find( { "members.userId": { $in: [userid] } }, {members:1} ) + .then((res)=>{ return res; }).catch((e)=>{}) + + teamsResult.map((_myTeam)=>{ + _myTeam.members.map((teamMember)=> { + if(!teamMember.userId.equals(userid)) + teamMemberIds.push( teamMember.userId ); + } ) + }) + + teamMembers = await userProfile.find({ _id: { $in: teamMemberIds } , isActive:true }, + {role:1,firstName:1,lastName:1,weeklycommittedHours:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + } + else { + if(userRole == 'Administrator'){ //All users except Owner and Core Team + const excludedRoles = ['Core Team', 'Owner']; + teamMembers = await userProfile.find({ isActive:true , role: { $nin: excludedRoles } }, + {role:1,firstName:1,lastName:1,weeklycommittedHours:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + } + else{ //'Core Team', 'Owner' //All users + teamMembers = await userProfile.find({ isActive:true}, + {role:1,firstName:1,lastName:1,weeklycommittedHours:1}) + .then((res)=>{ return res; }).catch((e)=>{}) + } + } + + teamMemberIds = teamMembers.map(member => member._id); + + const timeEntries = await timeentry.find({ + dateOfWork: { + $gte: pdtstart, + $lte: pdtend, + }, + personId: { $in: teamMemberIds } + }); + + let timeEntryByPerson = {} + timeEntries.map((timeEntry)=>{ + + let personIdStr = timeEntry.personId.toString(); + + if(timeEntryByPerson[personIdStr]==null) + timeEntryByPerson[personIdStr] = {tangibleSeconds:0,intangibleSeconds:0,totalSeconds:0}; + + if (timeEntry.isTangible === true) { + timeEntryByPerson[personIdStr].tangibleSeconds += timeEntry.totalSeconds; + } + timeEntryByPerson[personIdStr].totalSeconds += timeEntry.totalSeconds; + }) + + const teamMemberTasks = await Task.find({"resources.userID" : {$in : teamMemberIds }}, { 'resources.profilePic': 0 }) + .populate( { + path: 'wbsId', + select: 'projectId', + }) + const teamMemberTaskIds = teamMemberTasks.map(task => task._id); + const teamMemberTaskNotifications = await TaskNotification.find({"taskId" : {$in : teamMemberTaskIds }}) + + const taskNotificationByTaskNdUser = [] + teamMemberTaskNotifications.map(teamMemberTaskNotification => { + + let taskIdStr = teamMemberTaskNotification.taskId.toString(); + let userIdStr = teamMemberTaskNotification.userId.toString(); + let taskNdUserID = taskIdStr+","+userIdStr; + + if(taskNotificationByTaskNdUser[taskNdUserID]) { + taskNotificationByTaskNdUser[taskNdUserID].push(teamMemberTaskNotification) + } + else{ + taskNotificationByTaskNdUser[taskNdUserID] = [teamMemberTaskNotification] + } + + }) + + const taskByPerson = [] + + teamMemberTasks.map(teamMemberTask => { + + let projId = teamMemberTask.wbsId?.projectId; + let _teamMemberTask = {...teamMemberTask._doc} + _teamMemberTask.projectId = projId; + let taskIdStr = _teamMemberTask._id.toString(); + + teamMemberTask.resources.map(resource => { + + let resourceIdStr = resource.userID.toString(); + let taskNdUserID = taskIdStr+","+resourceIdStr; + _teamMemberTask.taskNotifications = taskNotificationByTaskNdUser[taskNdUserID] || [] + if(taskByPerson[resourceIdStr]) { + taskByPerson[resourceIdStr].push(_teamMemberTask) + } + else{ + taskByPerson[resourceIdStr] = [_teamMemberTask] + } + }) + }) + + + let teamMemberTasksData = []; + teamMembers.map((teamMember)=>{ + let obj = { + personId : teamMember._id, + role : teamMember.role, + name : teamMember.firstName + ' ' + teamMember.lastName, + weeklycommittedHours : teamMember.weeklycommittedHours, + totaltangibletime_hrs : ((timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds / 3600) || 0), + totaltime_hrs : ((timeEntryByPerson[teamMember._id.toString()]?.totalSeconds / 3600) || 0), + tasks : taskByPerson[teamMember._id.toString()] || [] + } + teamMemberTasksData.push(obj); + }) + + + return teamMemberTasksData; + + + // return myteam.aggregate([ + // { + // $match: { + // _id: userId, + // }, + // }, + // { + // $unwind: '$myteam', + // }, + // { + // $project: { + // _id: 0, + // personId: '$myteam._id', + // name: '$myteam.fullName', + // role: 1, + // }, + // }, + // // have personId, name, role + // { + // $lookup: { + // from: 'userProfiles', + // localField: 'personId', + // foreignField: '_id', + // as: 'persondata', + // }, + // }, + // { + // $match: { + // // dashboard tasks user roles hierarchy + // $or: [ + // { + // role: { $in: ['Owner', 'Core Team'] }, + // }, + // { + // $and: [ + // { + // role: 'Administrator', + // }, + // { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, + // ], + // }, + // { + // $and: [ + // { + // role: { $in: ['Manager', 'Mentor'] }, + // }, + // { + // 'persondata.0.role': { + // $nin: ['Manager', 'Mentor', 'Core Team', 'Administrator', 'Owner'], + // }, + // }, + // ], + // }, + // { 'persondata.0._id': userId }, + // { 'persondata.0.role': 'Volunteer' }, + // { 'persondata.0.isVisible': true }, + // ], + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // weeklycommittedHours: { + // $sum: [ + // { + // $arrayElemAt: ['$persondata.weeklycommittedHours', 0], + // }, + // { + // $ifNull: [{ $arrayElemAt: ['$persondata.missedHours', 0] }, 0], + // }, + // ], + // }, + // role: 1, + // }, + // }, + // { + // $lookup: { + // from: 'timeEntries', + // localField: 'personId', + // foreignField: 'personId', + // as: 'timeEntryData', + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // weeklycommittedHours: 1, + // timeEntryData: { + // $filter: { + // input: '$timeEntryData', + // as: 'timeentry', + // cond: { + // $and: [ + // { + // $gte: ['$$timeentry.dateOfWork', pdtstart], + // }, + // { + // $lte: ['$$timeentry.dateOfWork', pdtend], + // }, + // ], + // }, + // }, + // }, + // role: 1, + // }, + // }, + // { + // $unwind: { + // path: '$timeEntryData', + // preserveNullAndEmptyArrays: true, + // }, + // }, + // { + // $project: { + // personId: 1, + // name: 1, + // weeklycommittedHours: 1, + // totalSeconds: { + // $cond: [ + // { + // $gte: ['$timeEntryData.totalSeconds', 0], + // }, + // '$timeEntryData.totalSeconds', + // 0, + // ], + // }, + // isTangible: { + // $cond: [ + // { + // $gte: ['$timeEntryData.totalSeconds', 0], + // }, + // '$timeEntryData.isTangible', + // false, + // ], + // }, + // role: 1, + // }, + // }, + // { + // $addFields: { + // tangibletime: { + // $cond: [ + // { + // $eq: ['$isTangible', true], + // }, + // '$totalSeconds', + // 0, + // ], + // }, + // }, + // }, + // { + // $group: { + // _id: { + // personId: '$personId', + // weeklycommittedHours: '$weeklycommittedHours', + // name: '$name', + // role: '$role', + // }, + // totalSeconds: { + // $sum: '$totalSeconds', + // }, + // tangibletime: { + // $sum: '$tangibletime', + // }, + // }, + // }, + // { + // $project: { + // _id: 0, + // personId: '$_id.personId', + // name: '$_id.name', + // weeklycommittedHours: '$_id.weeklycommittedHours', + // totaltime_hrs: { + // $divide: ['$totalSeconds', 3600], + // }, + // totaltangibletime_hrs: { + // $divide: ['$tangibletime', 3600], + // }, + // role: '$_id.role', + // }, + // }, + // { + // $lookup: { + // from: 'tasks', + // localField: 'personId', + // foreignField: 'resources.userID', + // as: 'tasks', + // }, + // }, + // { + // $project: { + // tasks: { + // resources: { + // profilePic: 0, + // }, + // }, + // }, + // }, + // { + // $unwind: { + // path: '$tasks', + // preserveNullAndEmptyArrays: true, + // }, + // }, + // { + // $lookup: { + // from: 'wbs', + // localField: 'tasks.wbsId', + // foreignField: '_id', + // as: 'projectId', + // }, + // }, + // { + // $addFields: { + // 'tasks.projectId': { + // $cond: [ + // { $ne: ['$projectId', []] }, + // { $arrayElemAt: ['$projectId', 0] }, + // '$tasks.projectId', + // ], + // }, + // }, + // }, + // { + // $project: { + // projectId: 0, + // tasks: { + // projectId: { + // _id: 0, + // isActive: 0, + // modifiedDatetime: 0, + // wbsName: 0, + // createdDatetime: 0, + // __v: 0, + // }, + // }, + // }, + // }, + // { + // $addFields: { + // 'tasks.projectId': '$tasks.projectId.projectId', + // }, + // }, + // { + // $lookup: { + // from: 'taskNotifications', + // localField: 'tasks._id', + // foreignField: 'taskId', + // as: 'tasks.taskNotifications', + // }, + // }, + // { + // $group: { + // _id: '$personId', + // tasks: { + // $push: '$tasks', + // }, + // data: { + // $first: '$$ROOT', + // }, + // }, + // }, + // { + // $addFields: { + // 'data.tasks': { + // $filter: { + // input: '$tasks', + // as: 'task', + // cond: { $ne: ['$$task', {}] }, + // }, + // }, + // }, + // }, + // { + // $replaceRoot: { + // newRoot: '$data', + // }, + // }, + // ]); }; const getTasksForSingleUser = function (userId) { const pdtstart = moment() diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 1033bab2f..b4b2acb46 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -1,71 +1,68 @@ /* eslint-disable no-continue */ /* eslint-disable no-await-in-loop */ -const mongoose = require("mongoose"); -const moment = require("moment-timezone"); -const _ = require("lodash"); -const userProfile = require("../models/userProfile"); -const timeEntries = require("../models/timeentry"); -const badge = require("../models/badge"); -const myTeam = require("./helperModels/myTeam"); -const dashboardHelper = require("./dashboardhelper")(); -const reportHelper = require("./reporthelper")(); -const emailSender = require("../utilities/emailSender"); -const logger = require("../startup/logger"); -const hasPermission = require("../utilities/permissions"); -const Reason = require("../models/reason"); -const token = require("../models/profileInitialSetupToken"); - +const mongoose = require('mongoose'); +const moment = require('moment-timezone'); +const _ = require('lodash'); +const userProfile = require('../models/userProfile'); +const timeEntries = require('../models/timeentry'); +const badge = require('../models/badge'); +const myTeam = require('./helperModels/myTeam'); +const dashboardHelper = require('./dashboardhelper')(); +const reportHelper = require('./reporthelper')(); +const emailSender = require('../utilities/emailSender'); +const logger = require('../startup/logger'); +const Reason = require('../models/reason'); +const token = require('../models/profileInitialSetupToken'); +const cache = require('../utilities/nodeCache')(); const userHelper = function () { + // Update format to "MMM-DD-YY" from "YYYY-MMM-DD" (Confirmed with Jae) + const earnedDateBadge = () => { + const currentDate = new Date(Date.now()); + return moment(currentDate).tz('America/Los_Angeles').format('MMM-DD-YY'); + }; + + const getTeamMembers = function (user) { const userId = mongoose.Types.ObjectId(user._id); // var teamid = userdetails.teamId; return myTeam.findById(userId).select({ - "myTeam._id": 0, - "myTeam.role": 0, - "myTeam.fullName": 0, + 'myTeam._id': 0, + 'myTeam.role': 0, + 'myTeam.fullName': 0, _id: 0, }); }; - // Updated By: Shengwei - // Updated Date: 12/08/2023 - // Update format to "MMM-DD-YY" from "YYYY-MMM-DD" (Confirmed with Jae) - const earnedDateBadge = () => { - const currentDate = new Date(Date.now()); - return moment(currentDate).tz('America/Los_Angeles').format('MMM-DD-YY'); - }; - const getUserName = async function (userId) { const userid = mongoose.Types.ObjectId(userId); - return userProfile.findById(userid, "firstName lastName"); + return userProfile.findById(userid, 'firstName lastName'); }; const validateProfilePic = function (profilePic) { - const picParts = profilePic.split("base64"); + const picParts = profilePic.split('base64'); let result = true; const errors = []; if (picParts.length < 2) { return { result: false, - errors: "Invalid image", + errors: 'Invalid image', }; } // validate size const imageSize = picParts[1].length; - const sizeInBytes = - (4 * Math.ceil(imageSize / 3) * 0.5624896334383812) / 1024; + const sizeInBytes = (4 * Math.ceil(imageSize / 3) * 0.5624896334383812) / 1024; if (sizeInBytes > 50) { - errors.push("Image size should not exceed 50KB"); + errors.push('Image size should not exceed 50KB'); result = false; } - const imageType = picParts[0].split("/")[1]; - if (imageType !== "jpeg;" && imageType !== "png;") { - errors.push("Image type shoud be either jpeg or png."); + const imageType = picParts[0].split('/')[1]; + if (imageType !== 'jpeg;' && imageType !== 'png;') { + errors.push('Image type shoud be either jpeg or png.'); result = false; } @@ -80,13 +77,12 @@ const userHelper = function () { lastName, infringement, totalInfringements, - timeRemaining + timeRemaining, ) { - let final_paragraph = ""; + let final_paragraph = ''; if (timeRemaining == undefined) { - final_paragraph = - "Life happens and we understand that. That’s why we allow 5 of them before taking action. This action usually includes removal from our team though, so please let your direct supervisor know what happened and do your best to avoid future blue squares if you are getting close to 5 and wish to avoid termination. Each blue square drops off after a year.
"; + final_paragraph = 'Life happens and we understand that. That’s why we allow 5 of them before taking action. This action usually includes removal from our team though, so please let your direct supervisor know what happened and do your best to avoid future blue squares if you are getting close to 5 and wish to avoid termination. Each blue square drops off after a year.
'; } else { final_paragraph = `Life happens and we understand that. Please make up the missed hours this following week though to avoid getting another blue square. So you know what’s needed, the missing/incomplete hours (${timeRemaining} hours) have been added to your current week and this new weekly total can be seen at the top of your dashboard.
Reminder also that each blue square is removed from your profile 1 year after it was issued.
`; @@ -117,10 +113,10 @@ const userHelper = function () { * @return {void} */ const emailWeeklySummariesForAllUsers = async (weekIndex = 1) => { - const currentFormattedDate = moment().tz("America/Los_Angeles").format(); + const currentFormattedDate = moment().tz('America/Los_Angeles').format(); logger.logInfo( - `Job for emailing all users' weekly summaries starting at ${currentFormattedDate}` + `Job for emailing all users' weekly summaries starting at ${currentFormattedDate}`, ); const emails = []; @@ -128,19 +124,15 @@ const userHelper = function () { try { const results = await reportHelper.weeklySummaries(weekIndex, weekIndex); - let emailBody = "Total Valid Weekly Summaries: ${weeklySummariesCount}
` : `Total Valid Weekly Summaries: ${ - weeklySummariesCount || "No valid submissions yet!" + weeklySummariesCount || 'No valid submissions yet!' }
` } ${ hoursLogged >= weeklycommittedHours ? `Hours logged: ${hoursLogged.toFixed( - 2 + 2, )} / ${weeklycommittedHours}
` : `Hours logged: ${hoursLogged.toFixed( - 2 + 2, )} / ${weeklycommittedHours}
` } ${weeklySummaryMessage} @@ -238,10 +230,8 @@ const userHelper = function () { // Necessary because our version of node is outdated // and doesn't have String.prototype.replaceAll let emailString = [...new Set(emails)].toString(); - while (emailString.includes(",")) - emailString = emailString.replace(",", "\n"); - while (emailString.includes("\n")) - emailString = emailString.replace("\n", ", "); + while (emailString.includes(',')) { emailString = emailString.replace(',', '\n'); } + while (emailString.includes('\n')) { emailString = emailString.replace('\n', ', '); } emailBody += `\nThe HGN A.I. (and One Community)
`; emailSender( - "onecommunityglobal@gmail.com", + 'onecommunityglobal@gmail.com', subject, emailBody, null, null, - person.email + person.email, ); } } @@ -759,7 +745,7 @@ const userHelper = function () { current, firstName, lastName, - emailAddress + emailAddress, ) { if (!current) return; const newOriginal = original.toObject(); @@ -770,58 +756,58 @@ const userHelper = function () { newInfringements = _.differenceWith( newCurrent, newOriginal, - (arrVal, othVal) => arrVal._id.equals(othVal._id) + (arrVal, othVal) => arrVal._id.equals(othVal._id), ); newInfringements.forEach((element) => { emailSender( emailAddress, - "New Infringement Assigned", + 'New Infringement Assigned', getInfringementEmailBody( firstName, lastName, element, - totalInfringements + totalInfringements, ), null, - "onecommunityglobal@gmail.com", - emailAddress + 'onecommunityglobal@gmail.com', + emailAddress, ); }); }; const replaceBadge = async function (personId, oldBadgeId, newBadgeId) { userProfile.updateOne( - { _id: personId, "badgeCollection.badge": oldBadgeId }, + { _id: personId, 'badgeCollection.badge': oldBadgeId }, { $set: { - "badgeCollection.$.badge": newBadgeId, - "badgeCollection.$.lastModified": Date.now().toString(), - "badgeCollection.$.count": 1, + 'badgeCollection.$.badge': newBadgeId, + 'badgeCollection.$.lastModified': Date.now().toString(), + 'badgeCollection.$.count': 1, + 'badgeCollection.$.earnedDate': [earnedDateBadge()], }, }, (err) => { if (err) { throw new Error(err); } - } + }, ); }; const increaseBadgeCount = async function (personId, badgeId) { - console.log("Increase Badge Count", personId, badgeId); userProfile.updateOne( - { _id: personId, "badgeCollection.badge": badgeId }, + { _id: personId, 'badgeCollection.badge': badgeId }, { - $inc: { "badgeCollection.$.count": 1 }, - $set: { "badgeCollection.$.lastModified": Date.now().toString() }, - $push: { "badgeCollection.$.earnedDate": earnedDateBadge() }, + $inc: { 'badgeCollection.$.count': 1 }, + $set: { 'badgeCollection.$.lastModified': Date.now().toString() }, + $push: { 'badgeCollection.$.earnedDate': earnedDateBadge() }, }, (err) => { if (err) { console.log(err); } - } + }, ); }; @@ -829,9 +815,8 @@ const userHelper = function () { personId, badgeId, count = 1, - featured = false + featured = false, ) { - console.log("Adding Badge"); userProfile.findByIdAndUpdate( personId, { @@ -849,7 +834,7 @@ const userHelper = function () { if (err) { throw new Error(err); } - } + }, ); }; @@ -865,28 +850,54 @@ const userHelper = function () { if (err) { throw new Error(err); } - } + }, ); }; - const changeBadgeCount = async function (personId, badgeId, count) { +const changeBadgeCount = async function (personId, badgeId, count) { if (count === 0) { removeDupBadge(personId, badgeId); } else if (count) { - userProfile.updateOne( - { _id: personId, "badgeCollection.badge": badgeId }, - { - $set: { - "badgeCollection.$.count": count, - "badgeCollection.$.lastModified": Date.now().toString(), - }, - }, - (err) => { - if (err) { - throw new Error(err); + // Process exisiting earned date to match the new count + try { + const userInfo = await userProfile.findById(personId); + let newEarnedDate = []; + const recordToUpdate = userInfo.badgeCollection.find(item => item.badge._id.toString() === badgeId.toString()); + if (!recordToUpdate) { + throw new Error('Badge not found'); + } + const copyOfEarnedDate = recordToUpdate.earnedDate; + if (copyOfEarnedDate.length < count) { + // if the EarnedDate count is less than the new count, add a earned date to the end of the collection + while (copyOfEarnedDate.length < count) { + copyOfEarnedDate.push(earnedDateBadge()); + } + } else { + // if the EarnedDate count is greater than the new count, remove the oldest earned date of the collection until it matches the new count - 1 + while (copyOfEarnedDate.length >= count) { + copyOfEarnedDate.shift(); } + copyOfEarnedDate.push(earnedDateBadge()); } - ); + newEarnedDate = [...copyOfEarnedDate]; + userProfile.updateOne( + { _id: personId, 'badgeCollection.badge': badgeId }, + { + $set: { + 'badgeCollection.$.count': count, + 'badgeCollection.$.lastModified': Date.now().toString(), + 'badgeCollection.$.earnedDate': newEarnedDate, + }, + }, + (err) => { + if (err) { + throw new Error(err); + } + }, + ); + } catch (err) { + logger.logException(err); + } } }; @@ -897,7 +908,7 @@ const userHelper = function () { user, badgeCollection, hrs, - weeks + weeks, ) { // Check each Streak Greater than One to check if it works if (weeks < 3) { @@ -908,7 +919,7 @@ const userHelper = function () { .aggregate([ { $match: { - type: "X Hours for X Week Streak", + type: 'X Hours for X Week Streak', weeks: { $gt: 1, $lt: weeks }, totalHrs: hrs, }, @@ -916,9 +927,9 @@ const userHelper = function () { { $sort: { weeks: -1, totalHrs: -1 } }, { $group: { - _id: "$weeks", + _id: '$weeks', badges: { - $push: { _id: "$_id", hrs: "$totalHrs", weeks: "$weeks" }, + $push: { _id: '$_id', hrs: '$totalHrs', weeks: '$weeks' }, }, }, }, @@ -928,16 +939,16 @@ const userHelper = function () { streak.badges.every((bdge) => { for (let i = 0; i < badgeCollection.length; i += 1) { if ( - badgeCollection[i].badge?.type === - "X Hours for X Week Streak" && - badgeCollection[i].badge?.weeks === bdge.weeks && - bdge.hrs === hrs && - !removed + badgeCollection[i].badge?.type + === 'X Hours for X Week Streak' + && badgeCollection[i].badge?.weeks === bdge.weeks + && bdge.hrs === hrs + && !removed ) { changeBadgeCount( personId, badgeCollection[i].badge._id, - badgeCollection[i].badge.count - 1 + badgeCollection[i].badge.count - 1, ); removed = true; return false; @@ -950,24 +961,23 @@ const userHelper = function () { }; // 'No Infringement Streak', - const checkNoInfringementStreak = async function ( personId, user, - badgeCollection + badgeCollection, ) { let badgeOfType; for (let i = 0; i < badgeCollection.length; i += 1) { - if (badgeCollection[i].badge?.type === "No Infringement Streak") { + if (badgeCollection[i].badge?.type === 'No Infringement Streak') { if ( - badgeOfType && - badgeOfType.months <= badgeCollection[i].badge.months + badgeOfType + && badgeOfType.months <= badgeCollection[i].badge.months ) { removeDupBadge(personId, badgeOfType._id); badgeOfType = badgeCollection[i].badge; } else if ( - badgeOfType && - badgeOfType.months > badgeCollection[i].badge.months + badgeOfType + && badgeOfType.months > badgeCollection[i].badge.months ) { removeDupBadge(personId, badgeCollection[i].badge._id); } else if (!badgeOfType) { @@ -976,7 +986,7 @@ const userHelper = function () { } } await badge - .find({ type: "No Infringement Streak" }) + .find({ type: 'No Infringement Streak' }) .sort({ months: -1 }) .then((results) => { if (!Array.isArray(results) || !results.length) { @@ -988,19 +998,19 @@ const userHelper = function () { if (elem.months <= 12) { if ( - moment().diff(moment(user.createdDate), "months", true) >= - elem.months + moment().diff(moment(user.createdDate), 'months', true) + >= elem.months ) { if ( - user.infringements.length === 0 || - Math.abs( + user.infringements.length === 0 + || Math.abs( moment().diff( moment( - user.infringements[user.infringements?.length - 1].date + user.infringements[user.infringements?.length - 1].date, ), - "months", - true - ) + 'months', + true, + ), ) >= elem.months ) { if (badgeOfType) { @@ -1008,7 +1018,7 @@ const userHelper = function () { replaceBadge( personId, mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(elem._id) + mongoose.Types.ObjectId(elem._id), ); } return false; @@ -1019,29 +1029,29 @@ const userHelper = function () { } } else if (user?.infringements?.length === 0) { if ( - moment().diff(moment(user.createdDate), "months", true) >= - elem.months + moment().diff(moment(user.createdDate), 'months', true) + >= elem.months ) { if ( - user.oldInfringements.length === 0 || - Math.abs( + user.oldInfringements.length === 0 + || Math.abs( moment().diff( moment( user.oldInfringements[user.oldInfringements?.length - 1] - .date + .date, ), - "months", - true - ) - ) >= - elem.months - 12 + 'months', + true, + ), + ) + >= elem.months - 12 ) { if (badgeOfType) { if (badgeOfType._id.toString() !== elem._id.toString()) { replaceBadge( personId, mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(elem._id) + mongoose.Types.ObjectId(elem._id), ); } return false; @@ -1060,13 +1070,13 @@ const userHelper = function () { const checkMinHoursMultiple = async function ( personId, user, - badgeCollection + badgeCollection, ) { const badgesOfType = badgeCollection - .map((obj) => obj.badge) - .filter((badge) => badge.type === "Minimum Hours Multiple"); + .map(obj => obj.badge) + .filter(badgeItem => badgeItem.type === 'Minimum Hours Multiple'); await badge - .find({ type: "Minimum Hours Multiple" }) + .find({ type: 'Minimum Hours Multiple' }) .sort({ multiple: -1 }) .then((results) => { if (!Array.isArray(results) || !results.length) { @@ -1077,16 +1087,16 @@ const userHelper = function () { const elem = results[i]; // making variable elem accessible for below code if ( - user.lastWeekTangibleHrs / user.weeklycommittedHours >= - elem.multiple + user.lastWeekTangibleHrs / user.weeklycommittedHours + >= elem.multiple ) { const theBadge = badgesOfType.find( - (badge) => badge._id.toString() === elem._id.toString() + badgeItem => badgeItem._id.toString() === elem._id.toString(), ); return theBadge ? increaseBadgeCount( personId, - mongoose.Types.ObjectId(theBadge._id) + mongoose.Types.ObjectId(theBadge._id), ) : addBadge(personId, mongoose.Types.ObjectId(elem._id)); } @@ -1098,29 +1108,29 @@ const userHelper = function () { const checkPersonalMax = async function (personId, user, badgeCollection) { let badgeOfType; for (let i = 0; i < badgeCollection.length; i += 1) { - if (badgeCollection[i].badge?.type === "Personal Max") { + if (badgeCollection[i].badge?.type === 'Personal Max') { if (badgeOfType) { removeDupBadge(personId, badgeOfType._id); } } } - await badge.findOne({ type: "Personal Max" }).then((results) => { + await badge.findOne({ type: 'Personal Max' }).then((results) => { if ( - user.lastWeekTangibleHrs && - user.lastWeekTangibleHrs >= 1 && - user.lastWeekTangibleHrs === user.personalBestMaxHrs + user.lastWeekTangibleHrs + && user.lastWeekTangibleHrs >= 1 + && user.lastWeekTangibleHrs === user.personalBestMaxHrs ) { if (badgeOfType) { changeBadgeCount( personId, mongoose.Types.ObjectId(badgeOfType._id), - user.personalBestMaxHrs + user.personalBestMaxHrs, ); } else { addBadge( personId, mongoose.Types.ObjectId(results._id), - user.personalBestMaxHrs + user.personalBestMaxHrs, ); } } @@ -1131,17 +1141,17 @@ const userHelper = function () { const checkMostHrsWeek = async function (personId, user, badgeCollection) { if ( - user.weeklycommittedHours > 0 && - user.lastWeekTangibleHrs > user.weeklycommittedHours + user.weeklycommittedHours > 0 + && user.lastWeekTangibleHrs > user.weeklycommittedHours ) { const badgeOfType = badgeCollection - .filter((object) => object.badge.type === "Most Hrs in Week") - .map((object) => object.badge); - await badge.findOne({ type: "Most Hrs in Week" }).then((results) => { + .filter(object => object.badge.type === 'Most Hrs in Week') + .map(object => object.badge); + await badge.findOne({ type: 'Most Hrs in Week' }).then((results) => { userProfile .aggregate([ { $match: { isActive: true } }, - { $group: { _id: 1, maxHours: { $max: "$lastWeekTangibleHrs" } } }, + { $group: { _id: 1, maxHours: { $max: '$lastWeekTangibleHrs' } } }, ]) .then((userResults) => { if (badgeOfType.length > 1) { @@ -1149,13 +1159,13 @@ const userHelper = function () { } if ( - user.lastWeekTangibleHrs && - user.lastWeekTangibleHrs >= userResults[0].maxHours + user.lastWeekTangibleHrs + && user.lastWeekTangibleHrs >= userResults[0].maxHours ) { if (badgeOfType.length) { increaseBadgeCount( personId, - mongoose.Types.ObjectId(badgeOfType[0]._id) + mongoose.Types.ObjectId(badgeOfType[0]._id), ); } else { addBadge(personId, mongoose.Types.ObjectId(results._id)); @@ -1171,12 +1181,12 @@ const userHelper = function () { // Handle Increasing the 1 week streak badges const badgesOfType = []; for (let i = 0; i < badgeCollection.length; i += 1) { - if (badgeCollection[i].badge?.type === "X Hours for X Week Streak") { + if (badgeCollection[i].badge?.type === 'X Hours for X Week Streak') { badgesOfType.push(badgeCollection[i].badge); } } await badge - .find({ type: "X Hours for X Week Streak", weeks: 1 }) + .find({ type: 'X Hours for X Week Streak', weeks: 1 }) .sort({ totalHrs: -1 }) .then((results) => { results.every((elem) => { @@ -1201,13 +1211,13 @@ const userHelper = function () { // Check each Streak Greater than One to check if it works await badge .aggregate([ - { $match: { type: "X Hours for X Week Streak", weeks: { $gt: 1 } } }, + { $match: { type: 'X Hours for X Week Streak', weeks: { $gt: 1 } } }, { $sort: { weeks: -1, totalHrs: -1 } }, { $group: { - _id: "$weeks", + _id: '$weeks', badges: { - $push: { _id: "$_id", hrs: "$totalHrs", weeks: "$weeks" }, + $push: { _id: '$_id', hrs: '$totalHrs', weeks: '$weeks' }, }, }, }, @@ -1219,19 +1229,19 @@ const userHelper = function () { let badgeOfType; for (let i = 0; i < badgeCollection.length; i += 1) { if ( - badgeCollection[i].badge?.type === - "X Hours for X Week Streak" && - badgeCollection[i].badge?.weeks === bdge.weeks + badgeCollection[i].badge?.type + === 'X Hours for X Week Streak' + && badgeCollection[i].badge?.weeks === bdge.weeks ) { if ( - badgeOfType && - badgeOfType.totalHrs <= badgeCollection[i].badge.totalHrs + badgeOfType + && badgeOfType.totalHrs <= badgeCollection[i].badge.totalHrs ) { removeDupBadge(personId, badgeOfType._id); badgeOfType = badgeCollection[i].badge; } else if ( - badgeOfType && - badgeOfType.totalHrs > badgeCollection[i].badge.totalHrs + badgeOfType + && badgeOfType.totalHrs > badgeCollection[i].badge.totalHrs ) { removeDupBadge(personId, badgeCollection[i].badge._id); } else if (!badgeOfType) { @@ -1256,7 +1266,7 @@ const userHelper = function () { replaceBadge( personId, mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(bdge._id) + mongoose.Types.ObjectId(bdge._id), ); removePrevHrBadge( @@ -1264,7 +1274,7 @@ const userHelper = function () { user, badgeCollection, bdge.hrs, - bdge.weeks + bdge.weeks, ); } else if (!badgeOfType) { addBadge(personId, mongoose.Types.ObjectId(bdge._id)); @@ -1273,19 +1283,19 @@ const userHelper = function () { user, badgeCollection, bdge.hrs, - bdge.weeks + bdge.weeks, ); } else if (badgeOfType && badgeOfType.totalHrs === bdge.hrs) { increaseBadgeCount( personId, - mongoose.Types.ObjectId(badgeOfType._id) + mongoose.Types.ObjectId(badgeOfType._id), ); removePrevHrBadge( personId, user, badgeCollection, bdge.hrs, - bdge.weeks + bdge.weeks, ); } return false; @@ -1302,16 +1312,16 @@ const userHelper = function () { const checkLeadTeamOfXplus = async function ( personId, user, - badgeCollection + badgeCollection, ) { const leaderRoles = [ - "Mentor", - "Manager", - "Administrator", - "Owner", - "Core Team", + 'Mentor', + 'Manager', + 'Administrator', + 'Owner', + 'Core Team', ]; - const approvedRoles = ["Mentor", "Manager"]; + const approvedRoles = ['Mentor', 'Manager']; if (!approvedRoles.includes(user.role)) return; let teamMembers; @@ -1336,16 +1346,16 @@ const userHelper = function () { }); let badgeOfType; for (let i = 0; i < badgeCollection.length; i += 1) { - if (badgeCollection[i].badge?.type === "Lead a team of X+") { + if (badgeCollection[i].badge?.type === 'Lead a team of X+') { if ( - badgeOfType && - badgeOfType.people <= badgeCollection[i].badge.people + badgeOfType + && badgeOfType.people <= badgeCollection[i].badge.people ) { removeDupBadge(personId, badgeOfType._id); badgeOfType = badgeCollection[i].badge; } else if ( - badgeOfType && - badgeOfType.people > badgeCollection[i].badge.people + badgeOfType + && badgeOfType.people > badgeCollection[i].badge.people ) { removeDupBadge(personId, badgeCollection[i].badge._id); } else if (!badgeOfType) { @@ -1354,7 +1364,7 @@ const userHelper = function () { } } await badge - .find({ type: "Lead a team of X+" }) + .find({ type: 'Lead a team of X+' }) .sort({ people: -1 }) .then((results) => { if (!Array.isArray(results) || !results.length) { @@ -1364,14 +1374,14 @@ const userHelper = function () { if (teamMembers && teamMembers.length >= badge.people) { if (badgeOfType) { if ( - badgeOfType._id.toString() !== badge._id.toString() && - badgeOfType.people < badge.people + badgeOfType._id.toString() !== badge._id.toString() + && badgeOfType.people < badge.people ) { replaceBadge( personId, mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(badge._id) + mongoose.Types.ObjectId(badge._id), ); } return false; @@ -1388,39 +1398,39 @@ const userHelper = function () { const checkTotalHrsInCat = async function (personId, user, badgeCollection) { const hoursByCategory = user.hoursByCategory || {}; const categories = [ - "food", - "energy", - "housing", - "education", - "society", - "economics", - "stewardship", + 'food', + 'energy', + 'housing', + 'education', + 'society', + 'economics', + 'stewardship', ]; const badgesOfType = badgeCollection - .filter((object) => object.badge.type === "Total Hrs in Category") - .map((object) => object.badge); + .filter(object => object.badge.type === 'Total Hrs in Category') + .map(object => object.badge); categories.forEach(async (category) => { const categoryHrs = Object.keys(hoursByCategory).find( - (elem) => elem === category + elem => elem === category, ); let badgeOfType; for (let i = 0; i < badgeCollection.length; i += 1) { if ( - badgeCollection[i].badge?.type === "Total Hrs in Category" && - badgeCollection[i].badge?.category === category + badgeCollection[i].badge?.type === 'Total Hrs in Category' + && badgeCollection[i].badge?.category === category ) { if ( - badgeOfType && - badgeOfType.totalHrs <= badgeCollection[i].badge.totalHrs + badgeOfType + && badgeOfType.totalHrs <= badgeCollection[i].badge.totalHrs ) { removeDupBadge(personId, badgeOfType._id); badgeOfType = badgeCollection[i].badge; } else if ( - badgeOfType && - badgeOfType.totalHrs > badgeCollection[i].badge.totalHrs + badgeOfType + && badgeOfType.totalHrs > badgeCollection[i].badge.totalHrs ) { removeDupBadge(personId, badgeCollection[i].badge._id); } else if (!badgeOfType) { @@ -1432,7 +1442,7 @@ const userHelper = function () { const newCatg = category.charAt(0).toUpperCase() + category.slice(1); await badge - .find({ type: "Total Hrs in Category", category: newCatg }) + .find({ type: 'Total Hrs in Category', category: newCatg }) .sort({ totalHrs: -1 }) .then((results) => { @@ -1442,8 +1452,8 @@ const userHelper = function () { results.every((elem) => { if ( - hoursByCategory[categoryHrs] >= 100 && - hoursByCategory[categoryHrs] >= elem.totalHrs + hoursByCategory[categoryHrs] >= 100 + && hoursByCategory[categoryHrs] >= elem.totalHrs ) { let theBadge; for (let i = 0; i < badgesOfType.length; i += 1) { @@ -1458,13 +1468,13 @@ const userHelper = function () { } if (badgeOfType) { if ( - badgeOfType._id.toString() !== elem._id.toString() && - badgeOfType.totalHrs < elem.totalHrs + badgeOfType._id.toString() !== elem._id.toString() + && badgeOfType.totalHrs < elem.totalHrs ) { replaceBadge( personId, mongoose.Types.ObjectId(badgeOfType._id), - mongoose.Types.ObjectId(elem._id) + mongoose.Types.ObjectId(elem._id), ); } return false; @@ -1479,11 +1489,12 @@ const userHelper = function () { }; const awardNewBadges = async () => { - console.log("Awarding"); + console.log('Awarding'); try { + // This will be used in production to run task on all users const users = await userProfile .find({ isActive: true }) - .populate("badgeCollection.badge"); + .populate('badgeCollection.badge'); for (let i = 0; i < users.length; i += 1) { const user = users[i]; @@ -1496,6 +1507,10 @@ const userHelper = function () { await checkLeadTeamOfXplus(personId, user, badgeCollection); await checkXHrsForXWeeks(personId, user, badgeCollection); await checkNoInfringementStreak(personId, user, badgeCollection); + // remove cache after badge asssignment. + if (cache.hasCache(`user-${_id}`)) { + cache.removeCache(`user-${_id}`); + } } } catch (err) { logger.logException(err); @@ -1506,13 +1521,13 @@ const userHelper = function () { const userId = mongoose.Types.ObjectId(personId); const pdtstart = moment() - .tz("America/Los_Angeles") - .startOf("week") - .format("YYYY-MM-DD"); + .tz('America/Los_Angeles') + .startOf('week') + .format('YYYY-MM-DD'); const pdtend = moment() - .tz("America/Los_Angeles") - .endOf("week") - .format("YYYY-MM-DD"); + .tz('America/Los_Angeles') + .endOf('week') + .format('YYYY-MM-DD'); return timeEntries .find( @@ -1521,12 +1536,12 @@ const userHelper = function () { dateOfWork: { $gte: pdtstart, $lte: pdtend }, isTangible: true, }, - "totalSeconds" + 'totalSeconds', ) .then((results) => { const totalTangibleWeeklySeconds = results.reduce( (acc, { totalSeconds }) => acc + totalSeconds, - 0 + 0, ); return (totalTangibleWeeklySeconds / 3600).toFixed(2); }); @@ -1536,28 +1551,28 @@ const userHelper = function () { try { const users = await userProfile.find( { isActive: true, endDate: { $exists: true } }, - "_id isActive endDate" + '_id isActive endDate', ); for (let i = 0; i < users.length; i += 1) { const user = users[i]; const { endDate } = user; endDate.setHours(endDate.getHours() + 7); - if (moment().isAfter(moment(endDate).add(1, "days"))) { + if (moment().isAfter(moment(endDate).add(1, 'days'))) { await userProfile.findByIdAndUpdate( user._id, user.set({ isActive: false, }), - { new: true } + { new: true }, ); const id = user._id; const person = await userProfile.findById(id); - const lastDay = moment(person.endDate).format("YYYY-MM-DD"); + const lastDay = moment(person.endDate).format('YYYY-MM-DD'); logger.logInfo( `User with id: ${ user._id - } was de-acticated at ${moment().format()}.` + } was de-acticated at ${moment().format()}.`, ); const subject = `IMPORTANT:${person.firstName} ${person.lastName} has been deactivated in the Highest Good Network`; @@ -1573,12 +1588,12 @@ const userHelper = function () {The HGN A.I. (and One Community)
`; emailSender( - "onecommunityglobal@gmail.com", + 'onecommunityglobal@gmail.com', subject, emailBody, null, null, - person.email + person.email, ); } } @@ -1598,6 +1613,7 @@ const userHelper = function () { }; return { + changeBadgeCount, getUserName, getTeamMembers, validateProfilePic, @@ -1615,4 +1631,4 @@ const userHelper = function () { }; }; -module.exports = userHelper; \ No newline at end of file +module.exports = userHelper; diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js index 5e8b24916..dfaa8c73a 100644 --- a/src/models/bmdashboard/buildingInventoryItem.js +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -82,7 +82,7 @@ const largeItemBase = mongoose.model('largeItemBase', largeItemBaseSchema, 'buil // each document derived from this schema includes key field { __t: "material" } // ex: sand, stone, bricks, lumber, insulation -const buildingMaterial = smallItemBase.discriminator('material', new mongoose.Schema({ +const buildingMaterial = smallItemBase.discriminator('material_item', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock })); @@ -95,7 +95,7 @@ const buildingMaterial = smallItemBase.discriminator('material', new mongoose.Sc // each document derived from this schema includes key field { __t: "consumable" } // ex: screws, nails, staples -const buildingConsumable = smallItemBase.discriminator('consumable', new mongoose.Schema({ +const buildingConsumable = smallItemBase.discriminator('consumable_item', new mongoose.Schema({ stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock })); @@ -108,7 +108,7 @@ const buildingConsumable = smallItemBase.discriminator('consumable', new mongoos // each document derived from this schema includes key field { __t: "reusable" } // ex: hammers, screwdrivers, mallets, brushes, gloves -const buildingReusable = smallItemBase.discriminator('reusable', new mongoose.Schema({ +const buildingReusable = smallItemBase.discriminator('reusable_item', new mongoose.Schema({ stockDestroyed: { type: Number, default: 0 }, })); @@ -120,7 +120,7 @@ const buildingReusable = smallItemBase.discriminator('reusable', new mongoose.Sc // each document derived from this schema includes key field { __t: "tool" } // ex: power drills, wheelbarrows, shovels, jackhammers -const buildingTool = largeItemBase.discriminator('tool', new mongoose.Schema({ +const buildingTool = largeItemBase.discriminator('tool_item', new mongoose.Schema({ code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking })); @@ -134,7 +134,7 @@ const buildingTool = largeItemBase.discriminator('tool', new mongoose.Schema({ // items in this category are assumed to be rented // ex: tractors, excavators, bulldozers -const buildingEquipment = largeItemBase.discriminator('equipment', new mongoose.Schema({ +const buildingEquipment = largeItemBase.discriminator('equipment_item', new mongoose.Schema({ isTracked: { type: Boolean, required: true }, // has asset tracker assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) })); diff --git a/src/models/bmdashboard/buildingInventoryType.js b/src/models/bmdashboard/buildingInventoryType.js index 8d2364c64..bd125dfd3 100644 --- a/src/models/bmdashboard/buildingInventoryType.js +++ b/src/models/bmdashboard/buildingInventoryType.js @@ -2,12 +2,82 @@ const mongoose = require('mongoose'); const { Schema } = mongoose; -const buildingInventoryType = new Schema({ - category: { type: String, enum: ['Consumable', 'Material', 'Tool', 'Equipment'], required: true }, +//--------------------------- +// BASE INVENTORY TYPE SCHEMA +//--------------------------- + +// all schemas will inherit these properties +// all documents will live in buildingInventoryTypes collection + +const invTypeBaseSchema = new Schema({ name: { type: String, required: true }, - description: { type: String, required: true }, - unit: { type: String, required: true }, // unit of measurement + description: { type: String, required: true, maxLength: 150 }, imageUrl: String, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfiles' }, }); -module.exports = mongoose.model('buildingInventoryType', buildingInventoryType, 'buildingInventoryTypes'); +const invTypeBase = mongoose.model('invTypeBase', invTypeBaseSchema, 'buildingInventoryTypes'); + +//--------------------------- +// MATERIAL TYPE +//--------------------------- + +// ex: sand, stone, brick, lumber + +const materialType = invTypeBase.discriminator('material_type', new mongoose.Schema({ + category: { type: String, enum: ['Material'] }, + unit: { type: String, required: true }, // unit of measurement +})); + +//--------------------------- +// CONSUMABLE TYPE +//--------------------------- + +// ex: screws, nails, staples + +const consumableType = invTypeBase.discriminator('consumable_type', new mongoose.Schema({ + category: { type: String, enum: ['Consumable'] }, + size: { type: String, required: true }, +})); + +//--------------------------- +// REUSABLE TYPE +//--------------------------- + +// ex: gloves, brushes, hammers, screwdrivers + +const reusableType = invTypeBase.discriminator('reusable_type', new mongoose.Schema({ + category: { type: String, enum: ['Reusable'] }, +})); + +//--------------------------- +// TOOL TYPE +//--------------------------- + +// ex: shovels, wheelbarrows, power drills, jackhammers + +const toolType = invTypeBase.discriminator('tool_type', new mongoose.Schema({ + category: { type: String, enum: ['Tool'] }, + isPowered: { type: Boolean, required: true }, + powerSource: { type: String, required: () => this.isPowered }, // required if isPowered = true (syntax?) +})); + +//--------------------------- +// EQUIPMENT TYPE +//--------------------------- + +// ex: tractors, excavators + +const equipmentType = invTypeBase.discriminator('equipment_type', new mongoose.Schema({ + category: { type: String, enum: ['Equipment'] }, + fuelType: { type: String, enum: ['Diesel', 'Biodiesel', 'Gasoline', 'Natural Gas', 'Ethanol'], required: true }, +})); + +module.exports = { + invTypeBase, + materialType, + consumableType, + reusableType, + toolType, + equipmentType, +}; \ No newline at end of file diff --git a/src/models/timeentry.js b/src/models/timeentry.js index 83510773f..79504cc20 100644 --- a/src/models/timeentry.js +++ b/src/models/timeentry.js @@ -7,7 +7,9 @@ const TimeEntry = new Schema({ entryType: { type: String, required: true, default: 'default' }, personId: { type: Schema.Types.ObjectId, ref: 'userProfile' }, projectId: { type: Schema.Types.ObjectId, ref: 'project' }, - teamId: { type: Schema.Types.ObjectId, ref: 'team' }, + wbsId: { type: Schema.Types.ObjectId, ref: 'project' }, + taskId: { type: Schema.Types.ObjectId, default: null, ref: 'wbs' }, + teamId: { type: Schema.Types.ObjectId, ref: 'task' }, dateOfWork: { type: String, required: true }, totalSeconds: { type: Number }, notes: { type: String }, diff --git a/src/models/userProfile.js b/src/models/userProfile.js index e3f8d4a48..8a8de2e18 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -88,7 +88,7 @@ const userProfileSchema = new Schema({ lng: { type: Number, default: '' }, }, country: { type: String, default: '' }, - city: { type: String, default: '' } + city: { type: String, default: '' }, }, oldInfringements: [ diff --git a/src/routes/bmdashboard/bmInventoryTypeRouter.js b/src/routes/bmdashboard/bmInventoryTypeRouter.js index ceae439dc..e89cb6b74 100644 --- a/src/routes/bmdashboard/bmInventoryTypeRouter.js +++ b/src/routes/bmdashboard/bmInventoryTypeRouter.js @@ -1,12 +1,20 @@ const express = require('express'); -const routes = function (invType) { +const routes = function (baseInvType, matType, consType, reusType, toolType, equipType) { const inventoryTypeRouter = express.Router(); - const controller = require('../../controllers/bmdashboard/bmInventoryTypeController')(invType); + const controller = require('../../controllers/bmdashboard/bmInventoryTypeController')(baseInvType, matType, consType, reusType, toolType, equipType); + // Route for fetching all material types inventoryTypeRouter.route('/invtypes/materials') .get(controller.fetchMaterialTypes); + inventoryTypeRouter.route('/invtypes/equipment') + .post(controller.addEquipmentType); + + // Combined routes for getting a single inventory type and updating its name and unit of measurement + inventoryTypeRouter.route('/invtypes/material/:invtypeId') + .get(controller.fetchSingleInventoryType) + .put(controller.updateNameAndUnit); return inventoryTypeRouter; }; diff --git a/src/routes/bmdashboard/bmToolRouter.js b/src/routes/bmdashboard/bmToolRouter.js new file mode 100644 index 000000000..a1a30ea40 --- /dev/null +++ b/src/routes/bmdashboard/bmToolRouter.js @@ -0,0 +1,13 @@ +const express = require('express'); + +const routes = function (BuildingTool) { + const toolRouter = express.Router(); + const controller = require('../../controllers/bmdashboard/bmToolController')(BuildingTool); + + toolRouter.route('/tools/:toolId') + .get(controller.fetchSingleTool); + + return toolRouter; +}; + +module.exports = routes; diff --git a/src/routes/taskRouter.js b/src/routes/taskRouter.js index 46e467c9d..e04b499eb 100644 --- a/src/routes/taskRouter.js +++ b/src/routes/taskRouter.js @@ -43,8 +43,8 @@ const routes = function (task, userProfile) { wbsRouter.route('/tasks/moveTasks/:wbsId') .put(controller.moveTask); - wbsRouter.route('/tasks/userProfile') - .get(controller.getTasksByUserList); + wbsRouter.route('/tasks/user/:userId') + .get(controller.getTasksByUserId); wbsRouter.route('/user/:userId/teams/tasks') .get(controller.getTasksForTeamsByUser); diff --git a/src/routes/wbsRouter.js b/src/routes/wbsRouter.js index b78ada5b9..08bfdc7b5 100644 --- a/src/routes/wbsRouter.js +++ b/src/routes/wbsRouter.js @@ -14,6 +14,9 @@ const routes = function (wbs) { wbsRouter.route('/wbsId/:id') .get(controller.getWBSById); + wbsRouter.route('/wbs/user/:userId') + .get(controller.getWBSByUserId); + wbsRouter.route('/wbs').get(controller.getWBS); return wbsRouter; diff --git a/src/startup/routes.js b/src/startup/routes.js index 963531627..3d9cbc8c1 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -22,9 +22,19 @@ const mouseoverText = require('../models/mouseoverText'); const inventoryItemMaterial = require('../models/inventoryItemMaterial'); const mapLocations = require('../models/mapLocation'); const buildingProject = require('../models/bmdashboard/buildingProject'); -const buildingInventoryType = require('../models/bmdashboard/buildingInventoryType'); const buildingMaterial = require('../models/bmdashboard/buildingMaterial'); -const buildingInventoryItem = require('../models/bmdashboard/buildingInventoryItem'); +const { + invTypeBase, + materialType, + consumableType, + reusableType, + toolType, + equipmentType, +} = require('../models/bmdashboard/buildingInventoryType'); +const { + buildingConsumable, +} = require('../models/bmdashboard/buildingInventoryItem'); +const buildingTool = require('../models/bmdashboard/buildingTool'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile); const badgeRouter = require('../routes/badgeRouter')(badge); @@ -65,8 +75,9 @@ const mapLocationRouter = require('../routes/mapLocationsRouter')(mapLocations); const bmLoginRouter = require('../routes/bmdashboard/bmLoginRouter')(); const bmMaterialsRouter = require('../routes/bmdashboard/bmMaterialsRouter')(inventoryItemMaterial, buildingMaterial); const bmProjectRouter = require('../routes/bmdashboard/bmProjectRouter')(buildingProject); -const bmInventoryTypeRouter = require('../routes/bmdashboard/bmInventoryTypeRouter')(buildingInventoryType); -const bmConsumablesRouter = require('../routes/bmdashboard/bmConsumablesRouter')(buildingInventoryItem.buildingConsumable); +const bmConsumablesRouter = require('../routes/bmdashboard/bmConsumablesRouter')(buildingConsumable); +const bmInventoryTypeRouter = require('../routes/bmdashboard/bmInventoryTypeRouter')(invTypeBase, materialType, consumableType, reusableType, toolType, equipmentType); +const bmToolRouter = require('../routes/bmdashboard/bmToolRouter')(buildingTool); module.exports = function (app) { app.use('/api', forgotPwdRouter); @@ -103,6 +114,6 @@ module.exports = function (app) { app.use('/api/bm', bmMaterialsRouter); app.use('/api/bm', bmProjectRouter); app.use('/api/bm', bmInventoryTypeRouter); + app.use('/api/bm', bmToolRouter); app.use('/api/bm', bmConsumablesRouter); - -}; +}; \ No newline at end of file