diff --git a/src/controllers/taskController.js b/src/controllers/taskController.js index 9bcf071de..753dbba11 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 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 userProfile = require('../models/userProfile'); const taskController = function (Task) { const getTasks = (req, res) => { @@ -33,7 +33,7 @@ const taskController = function (Task) { const getWBSId = (req, res) => { const { wbsId } = req.params; - wbs.findById(wbsId) + WBS.findById(wbsId) .then(results => res.status(200).send(results)) .catch(error => res.status(404).send(error)); }; @@ -446,7 +446,7 @@ const taskController = function (Task) { }); 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(); }); @@ -803,31 +803,28 @@ const taskController = function (Task) { 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', - ).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); + Task.find({ + 'resources.userID': mongoose.Types.ObjectId(userId), + }, '-resources.profilePic') + .then((results) => { + 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,7 +834,7 @@ 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 { @@ -845,6 +842,7 @@ const taskController = function (Task) { res.status(200).send(singleUserData); } } catch (error) { + console.log(error); res.status(400).send(error); } }; @@ -872,8 +870,8 @@ 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))) { recipients.push(member.email); @@ -917,7 +915,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..0570afc00 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 } } }, { 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 } } }); + 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 = "

Weekly Summaries for all active users:

"; + let emailBody = '

Weekly Summaries for all active users:

'; - const weeklySummaryNotProvidedMessage = - '
Weekly Summary: Not provided!
'; + const weeklySummaryNotProvidedMessage = '
Weekly Summary: Not provided!
'; - const weeklySummaryNotRequiredMessage = - '
Weekly Summary: Not required for this user
'; + const weeklySummaryNotRequiredMessage = '
Weekly Summary: Not required for this user
'; - results.sort((a, b) => - `${a.firstName} ${a.lastName}`.localeCompare( - `${b.firstName} ${b.lastname}` - ) - ); + results.sort((a, b) => `${a.firstName} ${a.lastName}`.localeCompare( + `${b.firstName} ${b.lastname}`, + )); for (let i = 0; i < results.length; i += 1) { const result = results[i]; @@ -166,19 +158,19 @@ const userHelper = function () { const mediaUrlLink = mediaUrl ? `${mediaUrl}` - : "Not provided!"; + : 'Not provided!'; let weeklySummaryMessage = weeklySummaryNotProvidedMessage; const colorStyle = (() => { switch (weeklySummaryOption) { - case "Team": + case 'Team': return 'style="color: magenta;"'; - case "Not Required": + case 'Not Required': return 'style="color: green"'; - case "Required": - return ""; + case 'Required': + return ''; default: - return result.weeklySummaryNotReq ? 'style="color: green"' : ""; + return result.weeklySummaryNotReq ? 'style="color: green"' : ''; } })(); // weeklySummaries array should only have one item if any, hence weeklySummaries[0] needs be used to access it. @@ -189,16 +181,16 @@ const userHelper = function () {
Weekly Summary (for the week ending on ${moment(dueDate) - .tz("America/Los_Angeles") - .format("YYYY-MMM-DD")}): + .tz('America/Los_Angeles') + .format('YYYY-MMM-DD')}):
${summary}
`; } else if ( - weeklySummaryOption === "Not Required" || - (!weeklySummaryOption && result.weeklySummaryNotReq) + weeklySummaryOption === 'Not Required' + || (!weeklySummaryOption && result.weeklySummaryNotReq) ) { weeklySummaryMessage = weeklySummaryNotRequiredMessage; } @@ -219,16 +211,16 @@ const userHelper = function () { weeklySummariesCount === 8 ? `

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 += `\n
@@ -253,12 +243,12 @@ const userHelper = function () { `; emailSender( - "onecommunityglobal@gmail.com, sangam.pravah@gmail.com, onecommunityhospitality@gmail.com", - "Weekly Summaries for all active users...", + 'onecommunityglobal@gmail.com, sangam.pravah@gmail.com, onecommunityhospitality@gmail.com', + 'Weekly Summaries for all active users...', emailBody, null, null, - emailString + emailString, ); } catch (err) { logger.logException(err); @@ -279,8 +269,8 @@ const userHelper = function () { weeklySummaries: { $each: [ { - dueDate: moment().tz("America/Los_Angeles").endOf("week"), - summary: "", + dueDate: moment().tz('America/Los_Angeles').endOf('week'), + summary: '', }, ], $position: 0, @@ -288,7 +278,7 @@ const userHelper = function () { }, }, }) - .catch((error) => logger.logException(error)); + .catch(error => logger.logException(error)); }; /** @@ -299,34 +289,34 @@ const userHelper = function () { */ const assignBlueSquareForTimeNotMet = async () => { try { - const currentFormattedDate = moment().tz("America/Los_Angeles").format(); + const currentFormattedDate = moment().tz('America/Los_Angeles').format(); const currentUTCDate = moment - .tz("America/Los_Angeles") - .startOf("day") + .tz('America/Los_Angeles') + .startOf('day') .toISOString(); logger.logInfo( - `Job for assigning blue square for commitment not met starting at ${currentFormattedDate}` + `Job for assigning blue square for commitment not met starting at ${currentFormattedDate}`, ); const pdtStartOfLastWeek = moment() - .tz("America/Los_Angeles") - .startOf("week") - .subtract(1, "week"); + .tz('America/Los_Angeles') + .startOf('week') + .subtract(1, 'week'); const pdtEndOfLastWeek = moment() - .tz("America/Los_Angeles") - .endOf("week") - .subtract(1, "week"); + .tz('America/Los_Angeles') + .endOf('week') + .subtract(1, 'week'); const users = await userProfile.find( { isActive: true }, - "_id weeklycommittedHours weeklySummaries missedHours" + '_id weeklycommittedHours weeklySummaries missedHours', ); - //this part is supposed to be a for, so it'll be slower when sending emails, so the emails will not be - //targeted as spam - //There's no need to put Promise.all here + // this part is supposed to be a for, so it'll be slower when sending emails, so the emails will not be + // targeted as spam + // There's no need to put Promise.all here for (let i = 0; i < users.length; i += 1) { const user = users[i]; @@ -342,8 +332,8 @@ const userHelper = function () { let hasWeeklySummary = false; if ( - Array.isArray(user.weeklySummaries) && - user.weeklySummaries.length + Array.isArray(user.weeklySummaries) + && user.weeklySummaries.length ) { const { summary } = user.weeklySummaries[0]; if (summary) { @@ -357,13 +347,12 @@ const userHelper = function () { const results = await dashboardHelper.laborthisweek( personId, pdtStartOfLastWeek, - pdtEndOfLastWeek + pdtEndOfLastWeek, ); const { timeSpent_hrs: timeSpent } = results[0]; - const weeklycommittedHours = - user.weeklycommittedHours + (user.missedHours ?? 0); + const weeklycommittedHours = user.weeklycommittedHours + (user.missedHours ?? 0); const timeNotMet = timeSpent < weeklycommittedHours; let description; @@ -386,23 +375,23 @@ const userHelper = function () { lastWeekTangibleHrs: timeSpent || 0, }, }, - { new: true } + { new: true }, ); if ( - updateResult?.weeklySummaryOption === "Not Required" || - updateResult?.weeklySummaryNotReq + updateResult?.weeklySummaryOption === 'Not Required' + || updateResult?.weeklySummaryNotReq ) { hasWeeklySummary = true; } - const cutOffDate = moment().subtract(1, "year"); + const cutOffDate = moment().subtract(1, 'year'); const oldInfringements = []; for (let k = 0; k < updateResult?.infringements.length; k += 1) { if ( - updateResult?.infringements && - moment(updateResult?.infringements[k].date).diff(cutOffDate) >= 0 + updateResult?.infringements + && moment(updateResult?.infringements[k].date).diff(cutOffDate) >= 0 ) { oldInfringements.push(updateResult.infringements[k]); } else { @@ -418,35 +407,33 @@ const userHelper = function () { oldInfringements: { $each: oldInfringements, $slice: -10 }, }, }, - { new: true } + { new: true }, ); } if (timeNotMet || !hasWeeklySummary) { if (foundReason) { description = foundReason.reason; - } else { - if (timeNotMet && !hasWeeklySummary) { + } else if (timeNotMet && !hasWeeklySummary) { description = `System auto-assigned infringement for two reasons: not meeting weekly volunteer time commitment as well as not submitting a weekly summary. For the hours portion, you logged ${timeSpent.toFixed( - 2 + 2, )} hours against committed effort of ${weeklycommittedHours} hours in the week starting ${pdtStartOfLastWeek.format( - "dddd YYYY-MM-DD" - )} and ending ${pdtEndOfLastWeek.format("dddd YYYY-MM-DD")}.`; + 'dddd YYYY-MM-DD', + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}.`; } else if (timeNotMet) { description = `System auto-assigned infringement for not meeting weekly volunteer time commitment. You logged ${timeSpent.toFixed( - 2 + 2, )} hours against committed effort of ${weeklycommittedHours} hours in the week starting ${pdtStartOfLastWeek.format( - "dddd YYYY-MM-DD" - )} and ending ${pdtEndOfLastWeek.format("dddd YYYY-MM-DD")}.`; + 'dddd YYYY-MM-DD', + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}.`; } else { description = `System auto-assigned infringement for not submitting a weekly summary for the week starting ${pdtStartOfLastWeek.format( - "dddd YYYY-MM-DD" - )} and ending ${pdtEndOfLastWeek.format("dddd YYYY-MM-DD")}.`; + 'dddd YYYY-MM-DD', + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}.`; } - } const infringement = { - date: moment().utc().format("YYYY-MM-DD"), + date: moment().utc().format('YYYY-MM-DD'), description, }; @@ -457,47 +444,47 @@ const userHelper = function () { infringements: infringement, }, }, - { new: true } + { new: true }, ); - let emailBody = ""; - if (person.role == "Core Team" && timeRemaining > 0) { + let emailBody = ''; + if (person.role === 'Core Team' && timeRemaining > 0) { emailBody = getInfringementEmailBody( status.firstName, status.lastName, infringement, status.infringements.length, - timeRemaining + timeRemaining, ); } else { emailBody = getInfringementEmailBody( status.firstName, status.lastName, infringement, - status.infringements.length + status.infringements.length, ); } emailSender( status.email, - "New Infringement Assigned", + 'New Infringement Assigned', emailBody, null, - "onecommunityglobal@gmail.com", + 'onecommunityglobal@gmail.com', status.email, - null + null, ); const categories = await dashboardHelper.laborThisWeekByCategory( personId, pdtStartOfLastWeek, - pdtEndOfLastWeek + pdtEndOfLastWeek, ); if (Array.isArray(categories) && categories.length > 0) { await userProfile.findOneAndUpdate( { _id: personId, categoryTangibleHrs: { $exists: false } }, - { $set: { categoryTangibleHrs: [] } } + { $set: { categoryTangibleHrs: [] } }, ); } else { continue; @@ -507,20 +494,20 @@ const userHelper = function () { const elem = categories[j]; if (elem._id == null) { - elem._id = "Other"; + elem._id = 'Other'; } const updateResult2 = await userProfile.findOneAndUpdate( - { _id: personId, "categoryTangibleHrs.category": elem._id }, - { $inc: { "categoryTangibleHrs.$.hrs": elem.timeSpent_hrs } }, - { new: true } + { _id: personId, 'categoryTangibleHrs.category': elem._id }, + { $inc: { 'categoryTangibleHrs.$.hrs': elem.timeSpent_hrs } }, + { new: true }, ); if (!updateResult2) { await userProfile.findOneAndUpdate( { _id: personId, - "categoryTangibleHrs.category": { $ne: elem._id }, + 'categoryTangibleHrs.category': { $ne: elem._id }, }, { $addToSet: { @@ -529,7 +516,7 @@ const userHelper = function () { hrs: elem.timeSpent_hrs, }, }, - } + }, ); } } @@ -541,13 +528,13 @@ const userHelper = function () { // processWeeklySummaries for nonActive users try { - const inactiveUsers = await userProfile.find({ isActive: false }, "_id"); + const inactiveUsers = await userProfile.find({ isActive: false }, '_id'); for (let i = 0; i < inactiveUsers.length; i += 1) { const user = inactiveUsers[i]; await processWeeklySummariesByUserId( mongoose.Types.ObjectId(user._id), - false + false, ); } } catch (err) { @@ -557,28 +544,28 @@ const userHelper = function () { const applyMissedHourForCoreTeam = async () => { try { - const currentDate = moment().tz("America/Los_Angeles").format(); + const currentDate = moment().tz('America/Los_Angeles').format(); logger.logInfo( - `Job for applying missed hours for Core Team members starting at ${currentDate}` + `Job for applying missed hours for Core Team members starting at ${currentDate}`, ); const startOfLastWeek = moment() - .tz("America/Los_Angeles") - .startOf("week") - .subtract(1, "week") - .format("YYYY-MM-DD"); + .tz('America/Los_Angeles') + .startOf('week') + .subtract(1, 'week') + .format('YYYY-MM-DD'); const endOfLastWeek = moment() - .tz("America/Los_Angeles") - .endOf("week") - .subtract(1, "week") - .format("YYYY-MM-DD"); + .tz('America/Los_Angeles') + .endOf('week') + .subtract(1, 'week') + .format('YYYY-MM-DD'); const missedHours = await userProfile.aggregate([ { $match: { - role: "Core Team", + role: 'Core Team', isActive: true, }, }, @@ -592,16 +579,15 @@ const userHelper = function () { $match: { $expr: { $and: [ - { $eq: ["$isTangible", true] }, - { $gte: ["$dateOfWork", startOfLastWeek] }, - { $lte: ["$dateOfWork", endOfLastWeek] }, - { $in: ["$entryType", 'default', null] }, + { $eq: ['$isTangible', true] }, + { $gte: ['$dateOfWork', startOfLastWeek] }, + { $lte: ['$dateOfWork', endOfLastWeek] }, ], }, }, }, ], - as: "timeEntries", + as: 'timeEntries', }, }, { @@ -613,8 +599,8 @@ const userHelper = function () { $subtract: [ { $sum: [ - { $ifNull: ["$missedHours", 0] }, - "$weeklycommittedHours", + { $ifNull: ['$missedHours', 0] }, + '$weeklycommittedHours', ], }, { @@ -622,8 +608,8 @@ const userHelper = function () { { $sum: { $map: { - input: "$timeEntries", - in: "$$this.totalSeconds", + input: '$timeEntries', + in: '$$this.totalSeconds', }, }, }, @@ -657,13 +643,13 @@ const userHelper = function () { }; const deleteBlueSquareAfterYear = async () => { - const currentFormattedDate = moment().tz("America/Los_Angeles").format(); + const currentFormattedDate = moment().tz('America/Los_Angeles').format(); logger.logInfo( - `Job for deleting blue squares older than 1 year starting at ${currentFormattedDate}` + `Job for deleting blue squares older than 1 year starting at ${currentFormattedDate}`, ); - const cutOffDate = moment().subtract(1, "year").format("YYYY-MM-DD"); + const cutOffDate = moment().subtract(1, 'year').format('YYYY-MM-DD'); try { const results = await userProfile.updateMany( @@ -676,7 +662,7 @@ const userHelper = function () { }, }, }, - } + }, ); logger.logInfo(results); @@ -686,16 +672,16 @@ const userHelper = function () { }; const reActivateUser = async () => { - const currentFormattedDate = moment().tz("America/Los_Angeles").format(); + const currentFormattedDate = moment().tz('America/Los_Angeles').format(); logger.logInfo( - `Job for activating users based on scheduled re-activation date starting at ${currentFormattedDate}` + `Job for activating users based on scheduled re-activation date starting at ${currentFormattedDate}`, ); try { const users = await userProfile.find( { isActive: false, reactivationDate: { $exists: true } }, - "_id isActive reactivationDate" + '_id isActive reactivationDate', ); for (let i = 0; i < users.length; i += 1) { const user = users[i]; @@ -710,21 +696,21 @@ const userHelper = function () { endDate: user.endDate, }, }, - { new: true } + { new: true }, ); logger.logInfo( `User with id: ${user._id} was re-acticated at ${moment() - .tz("America/Los_Angeles") - .format()}.` + .tz('America/Los_Angeles') + .format()}.`, ); const id = user._id; const person = await userProfile.findById(id); - const endDate = moment(person.endDate).format("YYYY-MM-DD"); + const endDate = moment(person.endDate).format('YYYY-MM-DD'); logger.logInfo( `User with id: ${ user._id - } was re-acticated at ${moment().format()}.` + } was re-acticated at ${moment().format()}.`, ); const subject = `IMPORTANT:${person.firstName} ${person.lastName} has been RE-activated in the Highest Good Network`; @@ -740,12 +726,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, ); } } @@ -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/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/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;