diff --git a/src/controllers/timeEntryController.js b/src/controllers/timeEntryController.js index 6e8d36596..44a50fcfb 100644 --- a/src/controllers/timeEntryController.js +++ b/src/controllers/timeEntryController.js @@ -1,5 +1,6 @@ const moment = require('moment-timezone'); const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); const logger = require('../startup/logger'); const UserProfile = require('../models/userProfile'); const Project = require('../models/project'); @@ -419,7 +420,7 @@ const addEditHistory = async (
One Community
-ADMINISTRATIVE DETAILS:
Start Date: ${moment(userprofile.startDate).utc().format('M-D-YYYY')}
Role: ${userprofile.role}
@@ -593,7 +594,7 @@ const timeEntrycontroller = function (TimeEntry) { await timeEntry.save({ session }); if (userprofile) { - await userprofile.save({ session }); + await userprofile.save({ session, validateModifiedOnly: true }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); } @@ -866,7 +867,7 @@ const timeEntrycontroller = function (TimeEntry) { } await timeEntry.save({ session }); if (userprofile) { - await userprofile.save({ session }); + await userprofile.save({ session, validateModifiedOnly: true }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); @@ -939,7 +940,7 @@ const timeEntrycontroller = function (TimeEntry) { await timeEntry.remove({ session }); if (userprofile) { - await userprofile.save({ session }); + await userprofile.save({ session, validateModifiedOnly: true }); // since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time removeOutdatedUserprofileCache(userprofile._id.toString()); @@ -1062,40 +1063,115 @@ const timeEntrycontroller = function (TimeEntry) { }); }; - const getTimeEntriesForReports = function (req, res) { + const getTimeEntriesForReports =async function (req, res) { const { users, fromDate, toDate } = req.body; + const cacheKey = `timeEntry_${fromDate}_${toDate}`; + const timeentryCache=cacheClosure(); + const cacheData=timeentryCache.hasCache(cacheKey) + if(cacheData){ + const data = timeentryCache.getCache(cacheKey); + return res.status(200).send(data); + } + try { + const results = await TimeEntry.find( + { + personId: { $in: users }, + dateOfWork: { $gte: fromDate, $lte: toDate }, + }, + '-createdDateTime' // Exclude unnecessary fields + ) + .lean() // Returns plain JavaScript objects, not Mongoose documents + .populate({ + path: 'projectId', + select: '_id projectName', // Only return necessary fields from the project + }) + .exec(); // Executes the query + const data = results.map(element => { + const record = { + _id: element._id, + isTangible: element.isTangible, + personId: element.personId, + dateOfWork: element.dateOfWork, + hours: formatSeconds(element.totalSeconds)[0], + minutes: formatSeconds(element.totalSeconds)[1], + projectId: element.projectId?._id || '', + projectName: element.projectId?.projectName || '', + }; + return record; + }); + timeentryCache.setCache(cacheKey,data); + return res.status(200).send(data); + } catch (error) { + res.status(400).send(error); + } + }; + const getTimeEntriesForProjectReports = function (req, res) { + const { users, fromDate, toDate } = req.body; + + // Fetch only necessary fields and avoid bringing the entire document TimeEntry.find( { personId: { $in: users }, dateOfWork: { $gte: fromDate, $lte: toDate }, }, - ' -createdDateTime', + 'totalSeconds isTangible dateOfWork projectId', ) - .populate('projectId') - + .populate('projectId', 'projectName _id') + .lean() // lean() for better performance as we don't need Mongoose document methods .then((results) => { - const data = []; - - results.forEach((element) => { - const record = {}; - record._id = element._id; - record.isTangible = element.isTangible; - record.personId = element.personId._id; - record.dateOfWork = element.dateOfWork; + const data = results.map((element) => { + const record = { + isTangible: element.isTangible, + dateOfWork: element.dateOfWork, + projectId: element.projectId ? element.projectId._id : '', + projectName: element.projectId ? element.projectId.projectName : '', + }; + + // Convert totalSeconds to hours and minutes [record.hours, record.minutes] = formatSeconds(element.totalSeconds); - record.projectId = element.projectId ? element.projectId._id : ''; - record.projectName = element.projectId ? element.projectId.projectName : ''; - data.push(record); + + return record; }); res.status(200).send(data); }) .catch((error) => { - res.status(400).send(error); + res.status(400).send({ message: 'Error fetching time entries for project reports', error }); }); }; + const getTimeEntriesForPeopleReports = async function (req, res) { + try { + const { users, fromDate, toDate } = req.body; + + const results = await TimeEntry.find( + { + personId: { $in: users }, + dateOfWork: { $gte: fromDate, $lte: toDate }, + }, + 'personId totalSeconds isTangible dateOfWork', + ).lean(); // Use lean() for better performance + + const data = results + .map((entry) => { + const [hours, minutes] = formatSeconds(entry.totalSeconds); + return { + personId: entry.personId, + hours, + minutes, + isTangible: entry.isTangible, + dateOfWork: entry.dateOfWork, + }; + }) + .filter(Boolean); + + res.status(200).send(data); + } catch (error) { + res.status(400).send({ message: 'Error fetching time entries for people reports', error }); + } + }; + /** * Get time entries for a specified project */ @@ -1208,7 +1284,12 @@ const timeEntrycontroller = function (TimeEntry) { */ const getLostTimeEntriesForTeamList = function (req, res) { const { teams, fromDate, toDate } = req.body; - + const lostteamentryCache=cacheClosure() + const cacheKey = `LostTeamEntry_${fromDate}_${toDate}`; + const cacheData=lostteamentryCache.getCache(cacheKey) + if(cacheData){ + return res.status(200).send(cacheData) + } TimeEntry.find( { entryType: 'team', @@ -1217,7 +1298,7 @@ const timeEntrycontroller = function (TimeEntry) { isActive: { $ne: false }, }, ' -createdDateTime', - ) + ).lean() .populate('teamId') .sort({ lastModifiedDateTime: -1 }) .then((results) => { @@ -1234,7 +1315,8 @@ const timeEntrycontroller = function (TimeEntry) { [record.hours, record.minutes] = formatSeconds(element.totalSeconds); data.push(record); }); - res.status(200).send(data); + lostteamentryCache.setCache(cacheKey,data); + return res.status(200).send(data); }) .catch((error) => { res.status(400).send(error); @@ -1367,10 +1449,12 @@ const timeEntrycontroller = function (TimeEntry) { return newTotalIntangibleHrs; }; + const recalculationTaskQueue = []; + /** - * recalculate the hoursByCatefory for all users and update the field + * recalculate the hoursByCategory for all users and update the field */ - const recalculateHoursByCategoryAllUsers = async function (req, res) { + const recalculateHoursByCategoryAllUsers = async function (taskId) { const session = await mongoose.startSession(); session.startTransaction(); @@ -1385,18 +1469,60 @@ const timeEntrycontroller = function (TimeEntry) { await Promise.all(recalculationPromises); await session.commitTransaction(); - return res.status(200).send({ - message: 'finished the recalculation for hoursByCategory for all users', - }); + + const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); + if (recalculationTask) { + recalculationTask.status = 'Completed'; + recalculationTask.completionTime = new Date().toISOString(); + } } catch (err) { await session.abortTransaction(); + const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); + if (recalculationTask) { + recalculationTask.status = 'Failed'; + recalculationTask.completionTime = new Date().toISOString(); + } + logger.logException(err); - return res.status(500).send({ error: err.toString() }); } finally { session.endSession(); } }; + const startRecalculation = async function (req, res) { + const taskId = uuidv4(); + recalculationTaskQueue.push({ + taskId, + status: 'In progress', + startTime: new Date().toISOString(), + completionTime: null, + }); + if (recalculationTaskQueue.length > 10) { + recalculationTaskQueue.shift(); + } + + res.status(200).send({ + message: 'The recalculation task started in the background', + taskId, + }); + + setTimeout(() => recalculateHoursByCategoryAllUsers(taskId), 0); + }; + + const checkRecalculationStatus = async function (req, res) { + const { taskId } = req.params; + const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId); + if (recalculationTask) { + res.status(200).send({ + status: recalculationTask.status, + startTime: recalculationTask.startTime, + completionTime: recalculationTask.completionTime, + }); + } else { + res.status(404).send({ message: 'Task not found' }); + } + }; + /** * recalculate the totalIntangibleHrs for all users and update the field */ @@ -1441,9 +1567,12 @@ const timeEntrycontroller = function (TimeEntry) { getLostTimeEntriesForTeamList, backupHoursByCategoryAllUsers, backupIntangibleHrsAllUsers, - recalculateHoursByCategoryAllUsers, recalculateIntangibleHrsAllUsers, getTimeEntriesForReports, + getTimeEntriesForProjectReports, + getTimeEntriesForPeopleReports, + startRecalculation, + checkRecalculationStatus, }; }; diff --git a/src/helpers/dashboardhelper.js b/src/helpers/dashboardhelper.js index 533dbe367..dddb7cca2 100644 --- a/src/helpers/dashboardhelper.js +++ b/src/helpers/dashboardhelper.js @@ -181,14 +181,11 @@ const dashboardhelper = function () { _myTeam.members.forEach((teamMember) => { if (teamMember.userId.equals(userid) && teamMember.visible) isUserVisible = true; }); - if(isUserVisible) - { + if (isUserVisible) { _myTeam.members.forEach((teamMember) => { - if (!teamMember.userId.equals(userid)) - teamMemberIds.push(teamMember.userId); - }); - } - + if (!teamMember.userId.equals(userid)) teamMemberIds.push(teamMember.userId); + }); + } }); teamMembers = await userProfile.find( @@ -203,8 +200,8 @@ const dashboardhelper = function () { timeOffFrom: 1, timeOffTill: 1, endDate: 1, - } - + missedHours: 1, + }, ); } else { // 'Core Team', 'Owner' //All users @@ -220,7 +217,7 @@ const dashboardhelper = function () { timeOffFrom: 1, timeOffTill: 1, endDate: 1, - + missedHours: 1, }, ); } @@ -269,6 +266,7 @@ const dashboardhelper = function () { ? teamMember.weeklySummaries[0].summary !== '' : false, weeklycommittedHours: teamMember.weeklycommittedHours, + missedHours: teamMember.missedHours ?? 0, totaltangibletime_hrs: (timeEntryByPerson[teamMember._id.toString()]?.tangibleSeconds ?? 0) / 3600, totalintangibletime_hrs: @@ -309,255 +307,6 @@ const dashboardhelper = function () { console.log(error); return new Error(error); } - - // 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/taskHelper.js b/src/helpers/taskHelper.js index 34fb36be8..fefa9f021 100644 --- a/src/helpers/taskHelper.js +++ b/src/helpers/taskHelper.js @@ -112,9 +112,15 @@ const taskHelper = function () { ); sharedTeamsResult.forEach((_myTeam) => { + let hasTeamVisibility = false; _myTeam.members.forEach((teamMember) => { - if (!teamMember.userId.equals(userid)) teamMemberIds.push(teamMember.userId); + if (teamMember.userId.equals(userid) && teamMember.visible) hasTeamVisibility = true; }); + if (hasTeamVisibility) { + _myTeam.members.forEach((teamMember) => { + if (!teamMember.userId.equals(userid)) teamMemberIds.push(teamMember.userId); + }); + } }); teamMembers = await userProfile diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index ed9c52131..5195e8a37 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -146,14 +146,14 @@ const userHelper = function () { .localeData() .ordinal( totalInfringements, - )} blue square of 5 and that means you have ${totalInfringements - 5} hour(s) added to your - requirement this week. This is in addition to any hours missed for last week: - ${weeklycommittedHours} hours commitment + ${remainHr} hours owed for last week + ${totalInfringements - 5} hours + )} blue square of 5 and that means you have ${totalInfringements - 5} hour(s) added to your + requirement this week. This is in addition to any hours missed for last week: + ${weeklycommittedHours} hours commitment + ${remainHr} hours owed for last week + ${totalInfringements - 5} hours owed for this being your ${moment .localeData() .ordinal( totalInfringements, - )} blue square = ${hrThisweek + totalInfringements - 5} hours required for this week. + )} blue square = ${hrThisweek + totalInfringements - 5} hours required for this week. .`; } // bold description for 'System auto-assigned infringement for two reasons ....' and 'not submitting a weekly summary' and logged hrs @@ -204,7 +204,7 @@ const userHelper = function () {One Community
-ADMINISTRATIVE DETAILS:
Start Date: ${administrativeContent.startDate}
Role: ${administrativeContent.role}
@@ -656,7 +656,7 @@ const userHelper = function () { } // No extra hours is needed if blue squares isn't over 5. // length +1 is because new infringement hasn't been created at this stage. - const coreTeamExtraHour = Math.max(0, oldInfringements.length - 5); + const coreTeamExtraHour = Math.max(0, oldInfringements.length + 1 - 5); const utcStartMoment = moment(pdtStartOfLastWeek).add(1, 'second'); const utcEndMoment = moment(pdtEndOfLastWeek).subtract(1, 'day').subtract(1, 'second'); @@ -703,7 +703,7 @@ const userHelper = function () { .localeData() .ordinal( oldInfringements.length + 1, - )} blue square. So you should have completed ${weeklycommittedHours} hours and you completed ${timeSpent.toFixed( + )} blue square. So you should have completed ${weeklycommittedHours + coreTeamExtraHour} hours and you completed ${timeSpent.toFixed( 2, )} hours.`; } else { @@ -727,7 +727,7 @@ const userHelper = function () { .localeData() .ordinal( oldInfringements.length + 1, - )} blue square. So you should have completed ${weeklycommittedHours} hours and you completed ${timeSpent.toFixed( + )} blue square. So you should have completed ${weeklycommittedHours + coreTeamExtraHour} hours and you completed ${timeSpent.toFixed( 2, )} hours.`; } else { @@ -956,29 +956,54 @@ const userHelper = function () { $project: { _id: 1, missedHours: { - $max: [ - { - $subtract: [ - { - $sum: [{ $ifNull: ['$missedHours', 0] }, '$weeklycommittedHours'], - }, - { - $divide: [ - { - $sum: { - $map: { - input: '$timeEntries', - in: '$$this.totalSeconds', - }, + $let: { + vars: { + baseMissedHours: { + $max: [ + { + $subtract: [ + { + $sum: [{ $ifNull: ['$missedHours', 0] }, '$weeklycommittedHours'], }, - }, - 3600, - ], - }, + { + $divide: [ + { + $sum: { + $map: { + input: '$timeEntries', + in: '$$this.totalSeconds', + }, + }, + }, + 3600, + ], + }, + ], + }, + 0, + ], + }, + infringementsAdjustment: { + $cond: [ + { + $and: [ + { $gt: ['$infringements', null] }, + { $gt: [{ $size: '$infringements' }, 5] }, + ], + }, + { $subtract: [{ $size: '$infringements' }, 5] }, + 0, + ], + }, + }, + in: { + $cond: [ + { $gt: ['$$baseMissedHours', 0] }, + { $add: ['$$baseMissedHours', '$$infringementsAdjustment'] }, + '$$baseMissedHours', ], }, - 0, - ], + }, }, }, }, @@ -1024,7 +1049,7 @@ const userHelper = function () { }, ); - logger.logInfo(`Job deleting blue squares older than 1 year finished + logger.logInfo(`Job deleting blue squares older than 1 year finished at ${moment().tz('America/Los_Angeles').format()} \nReulst: ${JSON.stringify(results)}`); } catch (err) { logger.logException(err); @@ -1074,11 +1099,11 @@ const userHelper = function () { const emailBody = `Hi Admin!
This email is to let you know that ${person.firstName} ${person.lastName} has been made active again in the Highest Good Network application after being paused on ${endDate}.
- +If you need to communicate anything with them, this is their email from the system: ${person.email}.
- +Thanks!
- +The HGN A.I. (and One Community)
`; emailSender('onecommunityglobal@gmail.com', subject, emailBody, null, null, person.email); @@ -2037,35 +2062,77 @@ const userHelper = function () { email, recipients, isSet, + reactivationDate, + sendThreeWeeks, + followup, ) { - if (endDate && !isSet) { - const subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; - const emailBody = `Management,
+ let subject; + let emailBody; + recipients.push('onecommunityglobal@gmail.com'); + recipients = recipients.toString(); + if (reactivationDate) { + subject = `IMPORTANT: ${firstName} ${lastName} has been PAUSED in the Highest Good Network`; + emailBody = `Management,
+ +Please note that ${firstName} ${lastName} has been PAUSED in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.
+For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part until they return on ${moment(reactivationDate).format('M-D-YYYY')}.
-Please note that ${firstName} ${lastName} has been made inactive in the Highest Good Network as of ${endDate}. - Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.
-With Gratitude,
- +One Community
`; - recipients.push('onecommunityglobal@gmail.com'); - recipients = recipients.toString(); - emailSender(recipients, subject, emailBody, null, null, email); - } else if (isSet) { - const subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; + emailSender(email, subject, emailBody, null, recipients, email); + } else if (endDate && isSet && sendThreeWeeks) { + const subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; const emailBody = `Management,
-Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network ${endDate}. - For a smooth transition, please confirm all your work is being wrapped up with this individual and nothing further will be needed on their part after this date.
- +Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.
+This is more than 3 weeks from now, but you should still start confirming all your work is being wrapped up with this individual and nothing further will be needed on their part after this date.
+ +An additional reminder email will be sent in their final 2 weeks.
+ +With Gratitude,
+ +One Community
`; + emailSender(email, subject, emailBody, null, recipients, email); + + } else if (endDate && isSet && followup) { + subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; + emailBody = `Management,
+ +Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.
+This is coming up soon. For a smooth transition, please confirm all your work is wrapped up with this individual and nothing further will be needed on their part after this date.
+With Gratitude,
- +One Community
`; - recipients.push('onecommunityglobal@gmail.com'); - recipients = recipients.toString(); - emailSender(recipients, subject, emailBody, null, null, email); + emailSender(email, subject, emailBody, null, recipients, email); + + } else if (endDate && isSet ) { + subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; + emailBody = `Management,
+ +Please note that the final day for ${firstName} ${lastName} has been set in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.
+For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.
+ +With Gratitude,
+ +One Community
`; + emailSender(email, subject, emailBody, null, recipients, email); + + } else if(endDate){ + subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; + emailBody = `Management,
+ +Please note that ${firstName} ${lastName} has been made inactive in the Highest Good Network as ${moment(endDate).format('M-D-YYYY')}.
+For a smooth transition, Please confirm all your work with this individual has been wrapped up and nothing further is needed on their part.
+ +With Gratitude,
+ +One Community
`; + emailSender(email, subject, emailBody, null, recipients, email); + }; + } - }; const deActivateUser = async () => { try { @@ -2076,13 +2143,38 @@ const userHelper = function () { const recipients = emailReceivers.map((receiver) => receiver.email); const users = await userProfile.find( { isActive: true, endDate: { $exists: true } }, - '_id isActive endDate isSet', + '_id isActive endDate isSet finalEmailThreeWeeksSent reactivationDate', ); for (let i = 0; i < users.length; i += 1) { const user = users[i]; - const { endDate } = user; + const { endDate, finalEmailThreeWeeksSent } = user; endDate.setHours(endDate.getHours() + 7); - if (moment().isAfter(moment(endDate).add(1, 'days'))) { + // notify reminder set final day before 2 weeks + if(finalEmailThreeWeeksSent && moment().isBefore(moment(endDate).subtract(2, 'weeks')) && moment().isAfter(moment(endDate).subtract(3, 'weeks'))){ + const id = user._id; + const person = await userProfile.findById(id); + const lastDay = moment(person.endDate).format('YYYY-MM-DD'); + logger.logInfo(`User with id: ${user._id}'s final Day is set at ${moment().format()}.`); + person.teams.map(async (teamId) => { + const managementEmails = await userHelper.getTeamManagementEmail(teamId); + if (Array.isArray(managementEmails) && managementEmails.length > 0) { + managementEmails.forEach((management) => { + recipients.push(management.email); + }); + } + }); + sendDeactivateEmailBody( + person.firstName, + person.lastName, + lastDay, + person.email, + recipients, + person.isSet, + person.reactivationDate, + false, + true, + ); + } else if (moment().isAfter(moment(endDate).add(1, 'days'))) { try { await userProfile.findByIdAndUpdate( user._id, @@ -2115,6 +2207,8 @@ const userHelper = function () { person.email, recipients, person.isSet, + person.reactivationDate, + undefined, ); } } diff --git a/src/models/team.js b/src/models/team.js index 4d73615f5..109d93221 100644 --- a/src/models/team.js +++ b/src/models/team.js @@ -3,9 +3,9 @@ const mongoose = require('mongoose'); const { Schema } = mongoose; /** - * This schema represents a team in the system. - * - * Deprecated field: teamCode. Team code is no longer associated with a team. + * This schema represents a team in the system. + * + * Deprecated field: teamCode. Team code is no longer associated with a team. * Team code is used as a text string identifier in the user profile data model. */ const team = new Schema({ @@ -15,9 +15,10 @@ const team = new Schema({ modifiedDatetime: { type: Date, default: Date.now() }, members: [ { - userId: { type: mongoose.SchemaTypes.ObjectId, required: true }, + userId: { type: mongoose.SchemaTypes.ObjectId, required: true, index : true }, addDateTime: { type: Date, default: Date.now(), ref: 'userProfile' }, visible: { type : 'Boolean', default:true}, + }, ], // Deprecated field @@ -35,4 +36,5 @@ const team = new Schema({ }, }); + module.exports = mongoose.model('team', team, 'teams'); diff --git a/src/models/timeentry.js b/src/models/timeentry.js index ea5303b3a..1535ab13e 100644 --- a/src/models/timeentry.js +++ b/src/models/timeentry.js @@ -17,5 +17,7 @@ const TimeEntry = new Schema({ lastModifiedDateTime: { type: Date, default: Date.now }, isActive: { type: Boolean, default: true }, }); +TimeEntry.index({ personId: 1, dateOfWork: 1 }); +TimeEntry.index({ entryType: 1, teamId: 1, dateOfWork: 1, isActive: 1 }); module.exports = mongoose.model('timeEntry', TimeEntry, 'timeEntries'); diff --git a/src/models/title.js b/src/models/title.js index 64b9aed92..a41063aea 100644 --- a/src/models/title.js +++ b/src/models/title.js @@ -4,6 +4,7 @@ const { Schema } = mongoose; const title = new Schema({ titleName: { type: String, required: true }, + titleCode: { type: String, required: true }, teamCode: { type: String, require: true }, projectAssigned: { projectName: { type: String, required: true }, @@ -13,8 +14,7 @@ const title = new Schema({ teamAssiged: { teamName: { type: String }, _id: { type: String }, - }, - shortName: { type: String, require: true }, + }, }); diff --git a/src/models/userProfile.js b/src/models/userProfile.js index cc7136f54..3a529294a 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -27,6 +27,7 @@ const userProfileSchema = new Schema({ isActive: { type: Boolean, required: true, default: true }, isRehireable: { type: Boolean, default: true }, isSet: { type: Boolean, required: true, default: false }, + finalEmailThreeWeeksSent: { type: Boolean, required: true, default: false }, role: { type: String, required: true, diff --git a/src/routes/timeentryRouter.js b/src/routes/timeentryRouter.js index 0fd7db716..b5fd641ae 100644 --- a/src/routes/timeentryRouter.js +++ b/src/routes/timeentryRouter.js @@ -19,6 +19,14 @@ const routes = function (TimeEntry) { TimeEntryRouter.route('/TimeEntry/reports').post(controller.getTimeEntriesForReports); + TimeEntryRouter.route('/TimeEntry/reports/projects').post( + controller.getTimeEntriesForProjectReports, + ); + + TimeEntryRouter.route('/TimeEntry/reports/people').post( + controller.getTimeEntriesForPeopleReports, + ); + TimeEntryRouter.route('/TimeEntry/lostUsers').post(controller.getLostTimeEntriesForUserList); TimeEntryRouter.route('/TimeEntry/lostProjects').post( @@ -32,9 +40,11 @@ const routes = function (TimeEntry) { ); TimeEntryRouter.route('/TimeEntry/recalculateHoursAllUsers/tangible').post( - controller.recalculateHoursByCategoryAllUsers, + controller.startRecalculation, ); + TimeEntryRouter.route('/TimeEntry/checkStatus/:taskId').get(controller.checkRecalculationStatus); + TimeEntryRouter.route('/TimeEntry/recalculateHoursAllUsers/intangible').post( controller.recalculateIntangibleHrsAllUsers, );