diff --git a/src/controllers/currentWarningsController.js b/src/controllers/currentWarningsController.js new file mode 100644 index 000000000..ec76d3326 --- /dev/null +++ b/src/controllers/currentWarningsController.js @@ -0,0 +1,153 @@ +/* eslint-disable */ +const mongoose = require('mongoose'); +const userProfile = require('../models/userProfile'); + +const currentWarningsController = function (currentWarnings) { + const checkForDuplicates = (currentWarning, warnings) => { + const duplicateFound = warnings.some( + (warning) => warning.warningTitle.toLowerCase() === currentWarning, + ); + + return duplicateFound; + }; + + const checkIfSpecialCharacter = (warning) => { + return !/^[a-zA-Z][a-zA-Z0-9]*(?: [a-zA-Z0-9]+)*$/.test(warning); + }; + const getCurrentWarnings = async (req, res) => { + try { + const response = await currentWarnings.find({}); + + if (response.length === 0) { + return res.status(400).send({ message: 'no valid records' }); + } + return res.status(200).send({ currentWarningDescriptions: response }); + } catch (error) { + res.status(401).send({ message: error.message || error }); + } + }; + + const postNewWarningDescription = async (req, res) => { + try { + const { newWarning, activeWarning, isPermanent } = req.body; + + const warnings = await currentWarnings.find({}); + + if (warnings.length === 0) { + return res.status(400).send({ error: 'no valid records' }); + } + + const testWarning = checkIfSpecialCharacter(newWarning); + if (testWarning) { + return res.status(200).send({ + error: 'Warning cannot have special characters as the first letter', + }); + } + + if (checkForDuplicates(newWarning, warnings)) { + return res.status(200).send({ error: 'warning already exists' }); + } + + const newWarningDescription = new currentWarnings(); + newWarningDescription.warningTitle = newWarning; + newWarningDescription.activeWarning = activeWarning; + newWarningDescription.isPermanent = isPermanent; + + warnings.push(newWarningDescription); + await newWarningDescription.save(); + + return res.status(201).send({ newWarnings: warnings }); + } catch (error) { + return res.status(401).send({ message: error.message }); + } + }; + + const editWarningDescription = async (req, res) => { + try { + const { editedWarning } = req.body; + + const id = editedWarning._id; + + const warnings = await currentWarnings.find({}); + + if (warnings.length === 0) { + return res.status(400).send({ message: 'no valid records' }); + } + + const lowerCaseWarning = editedWarning.warningTitle.toLowerCase(); + const testWarning = checkIfSpecialCharacter(lowerCaseWarning); + + if (testWarning) { + return res.status(200).send({ + error: 'Warning cannot have special characters as the first letter', + }); + } + + if (checkForDuplicates(lowerCaseWarning, warnings)) { + return res.status(200).send({ error: 'warning already exists try a different name' }); + } + + await currentWarnings.findOneAndUpdate( + { _id: id }, + [{ $set: { warningTitle: lowerCaseWarning.trim() } }], + { new: true }, + ); + + res.status(201).send({ message: 'warning description was updated' }); + } catch (error) { + res.status(401).send({ message: error.message || error }); + } + }; + const updateWarningDescription = async (req, res) => { + try { + const { warningDescriptionId } = req.params; + + await currentWarnings.findOneAndUpdate( + { _id: warningDescriptionId }, + [{ $set: { activeWarning: { $not: '$activeWarning' } } }], + { new: true }, + ); + + res.status(201).send({ message: 'warning description was updated' }); + } catch (error) { + res.status(401).send({ message: error.message || error }); + } + }; + + const deleteWarningDescription = async (req, res) => { + try { + const { warningDescriptionId } = req.params; + const documentToDelete = await currentWarnings.findById(warningDescriptionId); + + await currentWarnings.deleteOne({ + _id: mongoose.Types.ObjectId(warningDescriptionId), + }); + + const deletedDescription = documentToDelete.warningTitle; + + await userProfile.updateMany( + { + 'warnings.description': deletedDescription, + }, + { + $pull: { + warnings: { description: deletedDescription }, + }, + }, + ); + + return res.status(200); + } catch (error) { + res.status(401).send({ message: error.message || error }); + } + }; + + return { + getCurrentWarnings, + postNewWarningDescription, + updateWarningDescription, + deleteWarningDescription, + editWarningDescription, + }; +}; +module.exports = currentWarningsController; diff --git a/src/controllers/warningsController.js b/src/controllers/warningsController.js index 381844883..08515c207 100644 --- a/src/controllers/warningsController.js +++ b/src/controllers/warningsController.js @@ -1,22 +1,45 @@ /* eslint-disable */ const mongoose = require('mongoose'); const userProfile = require('../models/userProfile'); +const currentWarnings = require('../models/currentWarnings'); +const emailSender = require('../utilities/emailSender'); +const userHelper = require('../helpers/userHelper')(); +let currentWarningDescriptions = null; +let currentUserName = null; +const emailTemplate = { + thirdWarning: { + subject: 'Third Warning', + body: `

This is the 3rd time the Admin team has requested the same thing from you. Specifically <“tracked area”>. Please carefully review the communications you’ve gotten about this so you understand what is being requested. Ask questions if anything isn’t clear, the Admin team is here to help.

+

Please also be sure to fix this from here on forward, asking for the same thing over and over requires administration that really shouldn’t be needed and will result in a blue square if it happens again.

+

With Gratitude,

+

One Community

`, + }, + fourthWarning: { + subject: 'Fourth Warning', + body: `

username !

+

This is the 3rd time the Admin team has requested the same thing from you. Specifically <“tracked area”>. Please carefully review the communications you’ve gotten about this so you understand what is being requested. Ask questions if anything isn’t clear, the Admin team is here to help.

+

Please also be sure to fix this from here on forward, asking for the same thing over and over requires administration that really shouldn’t be needed and will result in a blue square if it happens again.

+

With Gratitude,

+

One Community

`, + }, +}; +async function getWarningDescriptions() { + currentWarningDescriptions = await currentWarnings.find({}, { warningTitle: 1, _id: 0 }); +} -const descriptions = [ - 'Better Descriptions', - 'Log Time to Tasks', - 'Log Time as You Go', - 'Log Time to Action Items', - 'Intangible Time Log w/o Reason', -]; const warningsController = function (UserProfile) { const getWarningsByUserId = async function (req, res) { + currentWarningDescriptions = await currentWarnings.find({ + activeWarning: true, + }); + + currentWarningDescriptions = currentWarningDescriptions.map((a) => a.warningTitle); const { userId } = req.params; try { const { warnings } = await UserProfile.findById(userId); - const completedData = filterWarnings(warnings); + const { completedData } = filterWarnings(currentWarningDescriptions, warnings); if (!warnings) { return res.status(400).send({ message: 'no valiud records' }); @@ -32,13 +55,20 @@ const warningsController = function (UserProfile) { const { userId } = req.params; const { iconId, color, date, description } = req.body; + const { monitorData } = req.body; const record = await UserProfile.findById(userId); if (!record) { return res.status(400).send({ message: 'No valid records found' }); } - const updatedWarnings = await userProfile.findByIdAndUpdate( + const userAssignedWarning = { + firstName: record.firstName, + lastName: record.lastName, + email: record.email, + }; + + const updatedWarnings = await UserProfile.findByIdAndUpdate( { _id: userId, }, @@ -46,7 +76,24 @@ const warningsController = function (UserProfile) { { new: true, upsert: true }, ); - const completedData = filterWarnings(updatedWarnings.warnings); + const { completedData, sendEmail, size } = filterWarnings( + currentWarningDescriptions, + updatedWarnings.warnings, + iconId, + color, + ); + + const adminEmails = await getUserRoleByEmail(record); + if (sendEmail !== null) { + sendEmailToUser( + sendEmail, + description, + userAssignedWarning, + monitorData, + size, + adminEmails, + ); + } res.status(201).send({ message: 'success', warnings: completedData }); } catch (error) { @@ -69,8 +116,8 @@ const warningsController = function (UserProfile) { return res.status(400).send({ message: 'no valid records' }); } - const sortedWarnings = filterWarnings(warnings.warnings); - res.status(201).send({ message: 'succesfully deleted', warnings: sortedWarnings }); + const { completedData } = filterWarnings(currentWarningDescriptions, warnings.warnings); + res.status(201).send({ message: 'succesfully deleted', warnings: completedData }); } catch (error) { res.status(401).send({ message: error.message || error }); } @@ -83,19 +130,80 @@ const warningsController = function (UserProfile) { }; }; -// gests the dsecriptions key from the array -const getDescriptionKey = (val) => { - const descriptions = [ - 'Better Descriptions', - 'Log Time to Tasks', - 'Log Time as You Go', - 'Log Time to Action Items', - 'Intangible Time Log w/o Reason', - ]; - - return descriptions.indexOf(val); +//helper to get the team members admin emails +async function getUserRoleByEmail(user) { + //replacement for jae's email + const recipients = ['test@test.com']; + for (const teamId of user.teams) { + const managementEmails = await userHelper.getTeamManagementEmail(teamId); + if (Array.isArray(managementEmails) && managementEmails.length > 0) { + managementEmails.forEach((management) => { + recipients.push(management.email); + }); + } + } + + return [...new Set(recipients)]; +} + +//helper function to get the ordinal +function getOrdinal(n) { + const suffixes = ['th', 'st', 'nd', 'rd']; + const value = n % 100; + return n + (suffixes[(value - 20) % 10] || suffixes[value] || suffixes[0]); +} +const sendEmailToUser = ( + sendEmail, + warningDescription, + userAssignedWarning, + monitorData, + size, + adminEmails, +) => { + const ordinal = getOrdinal(size); + const subjectTitle = ordinal + ' Warning'; + + const currentUserName = `${userAssignedWarning.firstName} ${userAssignedWarning.lastName}`; + const emailTemplate = + sendEmail === 'issue warning' + ? `

Hello ${currentUserName},

+

This is the ${ordinal} time the Admin team has requested the same thing from you. Specifically, ${warningDescription}. Please carefully review the previous communications you’ve received to fully understand what is being requested. If anything is unclear, don’t hesitate to ask questions—the Admin team is here to assist.

+

Moving forward, please ensure this issue is resolved. Repeated requests for the same thing require unnecessary administrative attention and may result in a blue square being issued if it happens again.

+

The Admin member who issued the warning is ${monitorData.firstName} ${monitorData.lastName} and their email is ${monitorData.email}. Please comment on your Google Doc and tag them using this email if you have any questions.

+

With Gratitude,

+

One Community

` + : `

Hello ${currentUserName},

+

A blue square has been issued because this is the ${ordinal} time the Admin team has requested the same thing from you. Specifically, ${warningDescription}.

+

Moving forward, please ensure this is resolved. Repeated requests for the same thing require unnecessary administrative attention, will result in an additional blue square being issued, and could lead to termination.

+

Please carefully review the previous communications you’ve received to fully understand what is being requested. If anything is unclear, feel free to ask questions—the Admin team is here to help.

+

The Admin member who issued this blue square is ${monitorData.firstName} ${monitorData.lastName} and can be reached at ${monitorData.email}. If you have any questions, please comment on your Google Doc and tag them using this email.

+

With Gratitude,

+

One Community

`; + + if (sendEmail === 'issue warning') { + emailSender( + `${userAssignedWarning.email}`, + subjectTitle, + emailTemplate, + null, + adminEmails.toString(), + null, + ); + } else if (sendEmail === 'issue blue square') { + emailSender( + `${userAssignedWarning.email}`, + `Blue Square issued for ${warningDescription}`, + null, + emailTemplate, + adminEmails.toString(), + null, + ); + } }; +// gets the dsecriptions key from the array +const getDescriptionKey = (val) => currentWarningDescriptions.indexOf(val); + const sortKeysAlphabetically = (a, b) => getDescriptionKey(a) - getDescriptionKey(b); // method to see which color is first @@ -118,14 +226,29 @@ const sortByColorAndDate = (a, b) => { return colorComparison; }; -const filterWarnings = (warnings) => { +const filterWarnings = (currentWarningDescriptions, warnings, iconId = null, color = null) => { const warningsObject = {}; + let sendEmail = null; + let size = null; + warnings.forEach((warning) => { if (!warningsObject[warning.description]) { warningsObject[warning.description] = []; } warningsObject[warning.description].push(warning); + + if ( + warningsObject[warning.description].length >= 3 && + warning.iconId === iconId && + color === 'yellow' + ) { + sendEmail = 'issue warning'; + size = warningsObject[warning.description].length; + } else if (warning.iconId === iconId && color === 'red') { + sendEmail = 'issue blue square'; + size = warningsObject[warning.description].length; + } }); const warns = Object.keys(warningsObject) @@ -141,13 +264,13 @@ const filterWarnings = (warnings) => { const completedData = []; - for (const descrip of descriptions) { + for (const descrip of currentWarningDescriptions) { completedData.push({ title: descrip, warnings: warns[descrip] ? warns[descrip] : [], }); } - return completedData; + return { completedData, sendEmail, size }; }; module.exports = warningsController; diff --git a/src/cronjobs/userProfileJobs.js b/src/cronjobs/userProfileJobs.js index e7a8662a6..e773847d2 100644 --- a/src/cronjobs/userProfileJobs.js +++ b/src/cronjobs/userProfileJobs.js @@ -11,6 +11,7 @@ const userProfileJobs = () => { async () => { const SUNDAY = 0; // will change back to 0 after fix if (moment().tz('America/Los_Angeles').day() === SUNDAY) { + console.log('Running Cron Jobs'); await userhelper.assignBlueSquareForTimeNotMet(); await userhelper.applyMissedHourForCoreTeam(); await userhelper.emailWeeklySummariesForAllUsers(); diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 5195e8a37..168d704b1 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -2066,10 +2066,10 @@ const userHelper = function () { sendThreeWeeks, followup, ) { - let subject; - let emailBody; - recipients.push('onecommunityglobal@gmail.com'); - recipients = recipients.toString(); + 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,

@@ -2094,7 +2094,6 @@ const userHelper = function () {

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,

@@ -2106,8 +2105,7 @@ const userHelper = function () {

One Community

`; emailSender(email, subject, emailBody, null, recipients, email); - - } else if (endDate && isSet ) { + } else if (endDate && isSet) { subject = `IMPORTANT: The last day for ${firstName} ${lastName} has been set in the Highest Good Network`; emailBody = `

Management,

@@ -2118,8 +2116,7 @@ const userHelper = function () {

One Community

`; emailSender(email, subject, emailBody, null, recipients, email); - - } else if(endDate){ + } else if (endDate) { subject = `IMPORTANT: ${firstName} ${lastName} has been deactivated in the Highest Good Network`; emailBody = `

Management,

@@ -2130,9 +2127,8 @@ const userHelper = function () {

One Community

`; emailSender(email, subject, emailBody, null, recipients, email); - }; - } + }; const deActivateUser = async () => { try { @@ -2150,13 +2146,17 @@ const userHelper = function () { const { endDate, finalEmailThreeWeeksSent } = user; endDate.setHours(endDate.getHours() + 7); // notify reminder set final day before 2 weeks - if(finalEmailThreeWeeksSent && moment().isBefore(moment(endDate).subtract(2, 'weeks')) && moment().isAfter(moment(endDate).subtract(3, '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); + const managementEmails = await userHelper.getTeamManagementEmail(teamId); if (Array.isArray(managementEmails) && managementEmails.length > 0) { managementEmails.forEach((management) => { recipients.push(management.email); diff --git a/src/models/currentWarnings.js b/src/models/currentWarnings.js new file mode 100644 index 000000000..18a446199 --- /dev/null +++ b/src/models/currentWarnings.js @@ -0,0 +1,11 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const currentWarnings = new Schema({ + warningTitle: { type: String, required: true }, + activeWarning: { type: Boolean, required: true }, + isPermanent: { type: Boolean, required: true }, +}); + +module.exports = mongoose.model('currentWarning', currentWarnings, 'currentWarnings'); diff --git a/src/models/userProfile.js b/src/models/userProfile.js index 3a529294a..61e8e8f91 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -76,7 +76,7 @@ const userProfileSchema = new Schema({ startDate: { type: Date, required: true, - default () { + default() { return this.createdDate; }, }, @@ -128,6 +128,7 @@ const userProfileSchema = new Schema({ required: true, default: 'white', }, + iconId: { type: String, required: true }, }, ], location: { diff --git a/src/routes/curentWarningsRouter.js b/src/routes/curentWarningsRouter.js new file mode 100644 index 000000000..f1a004493 --- /dev/null +++ b/src/routes/curentWarningsRouter.js @@ -0,0 +1,23 @@ +const express = require('express'); + +const route = function (currentWarnings) { + const controller = require('../controllers/currentWarningsController')(currentWarnings); + + const currentWarningsRouter = express.Router(); + + currentWarningsRouter + .route('/currentWarnings') + .get(controller.getCurrentWarnings) + .post(controller.postNewWarningDescription); + + currentWarningsRouter.route('/currentWarnings/edit').put(controller.editWarningDescription); + + currentWarningsRouter + .route('/currentWarnings/:warningDescriptionId') + .delete(controller.deleteWarningDescription) + .put(controller.updateWarningDescription); + + return currentWarningsRouter; +}; + +module.exports = route; diff --git a/src/startup/routes.js b/src/startup/routes.js index b307ac4f4..8274188bc 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -15,6 +15,8 @@ const inventoryItemType = require('../models/inventoryItemType'); const role = require('../models/role'); const rolePreset = require('../models/rolePreset'); const ownerMessage = require('../models/ownerMessage'); +const currentWarnings = require('../models/currentWarnings'); + // Title const title = require('../models/title'); const blueSquareEmailAssignment = require('../models/BlueSquareEmailAssignment'); @@ -48,6 +50,7 @@ const followUp = require('../models/followUp'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile, project); const warningRouter = require('../routes/warningRouter')(userProfile); +const currentWarningsRouter = require('../routes/curentWarningsRouter')(currentWarnings); const badgeRouter = require('../routes/badgeRouter')(badge); const dashboardRouter = require('../routes/dashboardRouter')(weeklySummaryAIPrompt); const timeEntryRouter = require('../routes/timeentryRouter')(timeEntry); @@ -158,11 +161,12 @@ module.exports = function (app) { app.use('/api', isEmailExistsRouter); app.use('/api', mapLocationRouter); app.use('/api', warningRouter); + app.use('/api', currentWarningsRouter); app.use('/api', titleRouter); app.use('/api', timeOffRequestRouter); app.use('/api', followUpRouter); app.use('/api', blueSquareEmailAssignmentRouter); - app.use('/api/jobs', jobsRouter) + app.use('/api/jobs', jobsRouter); // bm dashboard app.use('/api/bm', bmLoginRouter); app.use('/api/bm', bmMaterialsRouter);