Oops, it looks like something happened and you’ve managed to get a blue square.
Date Assigned: ${infringement.date}
-Description: ${infringement.description}
+Description: ${formatTimeOffRequestsDescription(infringement.description)}
Total Infringements: This is your ${moment .localeData() .ordinal(totalInfringements)} blue square of 5.
@@ -355,6 +366,7 @@ const userHelper = function () { const weeklycommittedHours = user.weeklycommittedHours + (user.missedHours ?? 0); const timeNotMet = timeSpent < weeklycommittedHours; + let description; const timeRemaining = weeklycommittedHours - timeSpent; @@ -411,6 +423,33 @@ const userHelper = function () { ); } + const utcStartMoment = moment(pdtStartOfLastWeek).add(1, 'second'); + const utcEndMoment = moment(pdtStartOfLastWeek).subtract(1, 'second'); + + const requestsForTimeOff = await timeOffRequest.find({ + requestFor: personId, + startingDate: { $lte: utcStartMoment }, + endingDate: { $gte: utcEndMoment }, + }); + + const hasTimeOffRequest = requestsForTimeOff.length > 0; + let requestForTimeOff; + let requestForTimeOffStartingDate; + let requestForTimeOffEndingDate; + let requestForTimeOffreason; + + + if (hasTimeOffRequest) { + requestForTimeOff = requestsForTimeOff[0]; + requestForTimeOffStartingDate = moment( + requestForTimeOff.startingDate, + ).format('dddd YYYY-MM-DD'); + requestForTimeOffEndingDate = moment( + requestForTimeOff.endingDate, + ).format('dddd YYYY-MM-DD'); + requestForTimeOffreason = requestForTimeOff.reason; + } + if (timeNotMet || !hasWeeklySummary) { if (foundReason) { description = foundReason.reason; @@ -419,19 +458,23 @@ const userHelper = function () { 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')}.`; + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}. + ${hasTimeOffRequest ? `Notice: unavailable from ${requestForTimeOffStartingDate}, to ${requestForTimeOffEndingDate}, due to ${requestForTimeOffreason}` : ''}`; } else if (timeNotMet) { description = `System auto-assigned infringement for not meeting weekly volunteer time commitment. You logged ${timeSpent.toFixed( 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')}.`; + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}. + ${hasTimeOffRequest ? `Notice: unavailable from ${requestForTimeOffStartingDate}, to ${requestForTimeOffEndingDate}, due to ${requestForTimeOffreason}` : ''}`; } 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')}.`; + )} and ending ${pdtEndOfLastWeek.format('dddd YYYY-MM-DD')}. + ${hasTimeOffRequest ? `Notice: unavailable from ${requestForTimeOffStartingDate}, to ${requestForTimeOffEndingDate}, due to ${requestForTimeOffreason}` : ''}`; } + const infringement = { date: moment().utc().format('YYYY-MM-DD'), description, @@ -521,7 +564,8 @@ const userHelper = function () { } } } - } + } + await deleteOldTimeOffRequests(); } catch (err) { logger.logException(err); } @@ -843,10 +887,11 @@ const userHelper = function () { personId, { $pull: { - badgeCollection: { badge: badgeId }, - }, + badgeCollection: { _id: mongoose.Types.ObjectId(badgeId) } + } }, - (err) => { + { new: true }, + err => { if (err) { throw new Error(err); } @@ -1107,19 +1152,28 @@ const changeBadgeCount = async function (personId, badgeId, count) { // 'Personal Max', const checkPersonalMax = async function (personId, user, badgeCollection) { let badgeOfType; + let duplicateBadges = []; + for (let i = 0; i < badgeCollection.length; i += 1) { - if (badgeCollection[i].badge?.type === 'Personal Max') { - if (badgeOfType) { - removeDupBadge(personId, badgeOfType._id); + if (badgeCollection[i].badge?.type === "Personal Max") { + if (!badgeOfType) { + badgeOfType = badgeCollection[i]; + } else { + duplicateBadges.push(badgeCollection[i]); } } + for (let badge of duplicateBadges) { + await removeDupBadge(personId, badge._id); + } } 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, @@ -1127,11 +1181,8 @@ const changeBadgeCount = async function (personId, badgeId, count) { user.personalBestMaxHrs, ); } else { - addBadge( - personId, - mongoose.Types.ObjectId(results._id), - user.personalBestMaxHrs, - ); + addBadge(personId, mongoose.Types.ObjectId(results._id), user.personalBestMaxHrs); + } } }); @@ -1489,17 +1540,13 @@ const changeBadgeCount = async function (personId, badgeId, count) { }; const awardNewBadges = async () => { - 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'); - + const users = await userProfile.find({ isActive: true }).populate('badgeCollection.badge'); for (let i = 0; i < users.length; i += 1) { const user = users[i]; const { _id, badgeCollection } = user; const personId = mongoose.Types.ObjectId(_id); + await checkPersonalMax(personId, user, badgeCollection); await checkMostHrsWeek(personId, user, badgeCollection); await checkMinHoursMultiple(personId, user, badgeCollection); @@ -1519,15 +1566,10 @@ const changeBadgeCount = async function (personId, badgeId, count) { const getTangibleHoursReportedThisWeekByUserId = function (personId) { const userId = mongoose.Types.ObjectId(personId); + + const pdtstart = moment().tz('America/Los_Angeles').startOf('week').format('YYYY-MM-DD'); + const pdtend = moment().tz('America/Los_Angeles').endOf('week').format('YYYY-MM-DD'); - const pdtstart = moment() - .tz('America/Los_Angeles') - .startOf('week') - .format('YYYY-MM-DD'); - const pdtend = moment() - .tz('America/Los_Angeles') - .endOf('week') - .format('YYYY-MM-DD'); return timeEntries .find( @@ -1612,6 +1654,22 @@ const changeBadgeCount = async function (personId, badgeId, count) { } }; + const deleteOldTimeOffRequests = async () => { + const endOfLastWeek = moment() + .tz('America/Los_Angeles') + .endOf('week') + .subtract(1, 'week'); + + const utcEndMoment = moment(endOfLastWeek).add(1, 'second'); + console.log(utcEndMoment); + try { + await timeOffRequest.deleteMany({ endingDate: { $lte: utcEndMoment } }); + console.log('Deleted expired time off requests.'); + } catch (error) { + console.error('Error deleting expired time off requests:', error); + } + }; + return { changeBadgeCount, getUserName, @@ -1628,6 +1686,7 @@ const changeBadgeCount = async function (personId, badgeId, count) { awardNewBadges, getTangibleHoursReportedThisWeekByUserId, deleteExpiredTokens, + deleteOldTimeOffRequests, }; }; diff --git a/src/models/bmdashboard/buildingInventoryType.js b/src/models/bmdashboard/buildingInventoryType.js index bd125dfd3..821f3e57b 100644 --- a/src/models/bmdashboard/buildingInventoryType.js +++ b/src/models/bmdashboard/buildingInventoryType.js @@ -80,4 +80,4 @@ module.exports = { reusableType, toolType, equipmentType, -}; \ No newline at end of file +}; diff --git a/src/models/mapLocation.js b/src/models/mapLocation.js index 5f42678d0..f0689b72a 100644 --- a/src/models/mapLocation.js +++ b/src/models/mapLocation.js @@ -3,21 +3,19 @@ const mongoose = require('mongoose'); const { Schema } = mongoose; const capitalizeString = (s) => { - if (typeof s !== 'string') { return s; } const words = s.split(' '); - const capitalizedWords = words.map(word => { + const capitalizedWords = words.map((word) => { if (word.length > 0) { return word.charAt(0).toUpperCase() + word.slice(1).toLowerCase(); - } else { - return ''; } + return ''; }); const capitalizedString = capitalizedWords.join(' '); return capitalizedString; -} +}; const mapLocation = new Schema({ title: { diff --git a/src/models/permissionChangeLog.js b/src/models/permissionChangeLog.js index 3ca37e416..ee8597ee5 100644 --- a/src/models/permissionChangeLog.js +++ b/src/models/permissionChangeLog.js @@ -1,26 +1,27 @@ const mongoose = require('mongoose'); + const { Schema } = mongoose; const User = require('./userProfile'); -const rolesMergedPermissions = require('./role') +const rolesMergedPermissions = require('./role'); const PermissionChangeLog = new Schema({ logDateTime: { type: String, required: true }, - roleId: { - type: mongoose.Types.ObjectId, - ref: rolesMergedPermissions, - required: true + roleId: { + type: mongoose.Types.ObjectId, + ref: rolesMergedPermissions, + required: true, }, roleName: { type: String }, permissions: { type: [String], required: true }, permissionsAdded: { type: [String], required: true }, permissionsRemoved: { type: [String], required: true }, - requestorId: { + requestorId: { type: mongoose.Types.ObjectId, - ref: User + ref: User, }, requestorRole: { type: String }, - requestorEmail: { type: String, required: true}, + requestorEmail: { type: String, required: true }, }); -module.exports = mongoose.model('permissionChangeLog', PermissionChangeLog, 'permissionChangeLogs'); \ No newline at end of file +module.exports = mongoose.model('permissionChangeLog', PermissionChangeLog, 'permissionChangeLogs'); diff --git a/src/models/timeOffRequest.js b/src/models/timeOffRequest.js new file mode 100644 index 000000000..d87184b36 --- /dev/null +++ b/src/models/timeOffRequest.js @@ -0,0 +1,14 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const timeOffRequest = new Schema({ + requestFor: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + reason: { type: 'String', required: true }, + startingDate: { type: Date, required: true }, + endingDate: { type: Date }, + duration: { type: Number, required: true }, // in weeks + +}); + +module.exports = mongoose.model('timeOffRequest', timeOffRequest, 'timeOffRequests'); diff --git a/src/models/userProfile.js b/src/models/userProfile.js index 633a81026..61269a990 100644 --- a/src/models/userProfile.js +++ b/src/models/userProfile.js @@ -1,9 +1,9 @@ -const mongoose = require("mongoose"); -const moment = require("moment-timezone"); +const mongoose = require('mongoose'); +const moment = require('moment-timezone'); const { Schema } = mongoose; -const validate = require("mongoose-validator"); -const bcrypt = require("bcryptjs"); +const validate = require('mongoose-validator'); +const bcrypt = require('bcryptjs'); const SALT_ROUNDS = 10; const nextDay = new Date(); @@ -15,12 +15,11 @@ const userProfileSchema = new Schema({ required: true, validate: { validator(v) { - const passwordregex = - /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/; + const passwordregex = /(?=^.{8,}$)((?=.*\d)|(?=.*\W+))(?![.\n])(?=.*[A-Z])(?=.*[a-z]).*$/; return passwordregex.test(v); }, message: - "{VALUE} is not a valid password!password should be at least 8 charcaters long with uppercase, lowercase and number/special char.", + '{VALUE} is not a valid password!password should be at least 8 charcaters long with uppercase, lowercase and number/special char.', }, }, isActive: { type: Boolean, required: true, default: true }, @@ -49,7 +48,7 @@ const userProfileSchema = new Schema({ required: true, unique: true, validate: [ - validate({ validator: "isEmail", message: "Email address is invalid" }), + validate({ validator: 'isEmail', message: 'Email address is invalid' }), ], }, weeklycommittedHours: { type: Number, default: 10 }, @@ -67,11 +66,11 @@ const userProfileSchema = new Schema({ { _id: Schema.Types.ObjectId, Name: String, Link: { type: String } }, ], adminLinks: [{ _id: Schema.Types.ObjectId, Name: String, Link: String }], - teams: [{ type: mongoose.SchemaTypes.ObjectId, ref: "team" }], - projects: [{ type: mongoose.SchemaTypes.ObjectId, ref: "project" }], + teams: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'team' }], + projects: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'project' }], badgeCollection: [ { - badge: { type: mongoose.SchemaTypes.ObjectId, ref: "badge" }, + badge: { type: mongoose.SchemaTypes.ObjectId, ref: 'badge' }, count: { type: Number, default: 0 }, earnedDate: { type: Array, default: [] }, lastModified: { type: Date, required: true, default: Date.now() }, @@ -90,13 +89,13 @@ const userProfileSchema = new Schema({ }, ], location: { - userProvided: { type: String, default: "" }, + userProvided: { type: String, default: '' }, coords: { - lat: { type: Number, default: "" }, - lng: { type: Number, default: "" }, + lat: { type: Number, default: '' }, + lng: { type: Number, default: '' }, }, - country: { type: String, default: "" }, - city: { type: String, default: "" }, + country: { type: String, default: '' }, + city: { type: String, default: '' }, }, oldInfringements: [ { @@ -118,7 +117,7 @@ const userProfileSchema = new Schema({ dueDate: { type: Date, required: true, - default: moment().tz("America/Los_Angeles").endOf("week"), + default: moment().tz('America/Los_Angeles').endOf('week'), }, summary: { type: String }, uploadDate: { type: Date }, @@ -148,17 +147,17 @@ const userProfileSchema = new Schema({ category: { type: String, enum: [ - "Food", - "Energy", - "Housing", - "Education", - "Society", - "Economics", - "Stewardship", - "Other", - "Unspecified", + 'Food', + 'Energy', + 'Housing', + 'Education', + 'Society', + 'Economics', + 'Stewardship', + 'Other', + 'Unspecified', ], - default: "Other", + default: 'Other', }, hrs: { type: Number, default: 0 }, }, @@ -169,27 +168,27 @@ const userProfileSchema = new Schema({ date: { type: Date, required: true, - default: moment().tz("America/Los_Angeles").toDate(), + default: moment().tz('America/Los_Angeles').toDate(), }, initialSeconds: { type: Number, required: true }, newSeconds: { type: Number, required: true }, }, ], weeklySummaryNotReq: { type: Boolean, default: false }, - timeZone: { type: String, required: true, default: "America/Los_Angeles" }, + timeZone: { type: String, required: true, default: 'America/Los_Angeles' }, isVisible: { type: Boolean, default: false }, weeklySummaryOption: { type: String }, - bioPosted: { type: String, default: "default" }, + bioPosted: { type: String, default: 'default' }, isFirstTimelog: { type: Boolean, default: true }, teamCode: { type: String, - default: "", + default: '', validate: { validator(v) { const teamCoderegex = /^([a-zA-Z]-[a-zA-Z]{3}|[a-zA-Z]{5})$|^$/; return teamCoderegex.test(v); }, - message: "Please enter a code in the format of A-AAA or AAAAA", + message: 'Please enter a code in the format of A-AAA or AAAAA', }, }, infoCollections: [ @@ -204,22 +203,22 @@ const userProfileSchema = new Schema({ timeOffTill: { type: Date, default: undefined }, }); -userProfileSchema.pre("save", function (next) { +userProfileSchema.pre('save', function (next) { const user = this; - if (!user.isModified("password")) return next(); + if (!user.isModified('password')) return next(); return bcrypt .genSalt(SALT_ROUNDS) - .then((result) => bcrypt.hash(user.password, result)) + .then(result => bcrypt.hash(user.password, result)) .then((hash) => { user.password = hash; return next(); }) - .catch((error) => next(error)); + .catch(error => next(error)); }); module.exports = mongoose.model( - "userProfile", + 'userProfile', userProfileSchema, - "userProfiles" + 'userProfiles', ); diff --git a/src/routes/permissionChangeLogsRouter.js b/src/routes/permissionChangeLogsRouter.js index 8c1f46219..50ed7696b 100644 --- a/src/routes/permissionChangeLogsRouter.js +++ b/src/routes/permissionChangeLogsRouter.js @@ -1,14 +1,14 @@ const express = require('express'); const routes = function (permissionChangeLog) { - const controller = require('../controllers/permissionChangeLogsController')(permissionChangeLog) + const controller = require('../controllers/permissionChangeLogsController')(permissionChangeLog); - const permissionChangeLogRouter = express.Router() + const permissionChangeLogRouter = express.Router(); - permissionChangeLogRouter.route("/permissionChangeLogs/:userId") - .get(controller.getPermissionChangeLogs) + permissionChangeLogRouter.route('/permissionChangeLogs/:userId') + .get(controller.getPermissionChangeLogs); - return permissionChangeLogRouter -} + return permissionChangeLogRouter; +}; -module.exports = routes \ No newline at end of file +module.exports = routes; diff --git a/src/routes/roleRouter.js b/src/routes/roleRouter.js index c9d4f963f..d2ebff196 100644 --- a/src/routes/roleRouter.js +++ b/src/routes/roleRouter.js @@ -1,5 +1,5 @@ const express = require('express'); -const changedPermissionsLogger = require('../utilities/logPermissionChangeByAccount') +const changedPermissionsLogger = require('../utilities/logPermissionChangeByAccount'); const routes = function (role) { const controller = require('../controllers/rolesController')(role); @@ -11,7 +11,7 @@ const routes = function (role) { RolesRouter.route('/roles/:roleId') .get(controller.getRoleById) - .patch(changedPermissionsLogger,controller.updateRoleById) + .patch(changedPermissionsLogger, controller.updateRoleById) .delete(controller.deleteRoleById); return RolesRouter; }; diff --git a/src/routes/timeOffRequestRouter.js b/src/routes/timeOffRequestRouter.js new file mode 100644 index 000000000..1dd27f05b --- /dev/null +++ b/src/routes/timeOffRequestRouter.js @@ -0,0 +1,26 @@ +const express = require('express'); + + +const routes = function (timeOffRequest) { + const timeOffRequestRouter = express.Router(); + const controller = require('../controllers/timeOffRequestController')(timeOffRequest); + + timeOffRequestRouter.route('/setTimeOffRequest') + .post(controller.setTimeOffRequest); + + timeOffRequestRouter.route('/getTimeOffRequests') + .get(controller.getTimeOffRequests); + + timeOffRequestRouter.route('/getTimeOffRequest/:id') + .get(controller.getTimeOffRequestbyId); + + timeOffRequestRouter.route('/updateTimeOffRequest/:id') + .post(controller.updateTimeOffRequestById); + + timeOffRequestRouter.route('/deleteTimeOffRequest/:id') + .delete(controller.deleteTimeOffRequestById); + + return timeOffRequestRouter; +}; + +module.exports = routes; diff --git a/src/startup/routes.js b/src/startup/routes.js index 1860d7060..de4dd0377 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -39,6 +39,7 @@ const { buildingTool, } = require('../models/bmdashboard/buildingInventoryItem'); // const buildingTool = require('../models/bmdashboard/buildingTool'); +const timeOffRequest = require('../models/timeOffRequest'); const userProfileRouter = require('../routes/userProfileRouter')(userProfile); const badgeRouter = require('../routes/badgeRouter')(badge); @@ -74,6 +75,7 @@ const reasonRouter = require('../routes/reasonRouter')(reason, userProfile); const mouseoverTextRouter = require('../routes/mouseoverTextRouter')(mouseoverText); const mapLocationRouter = require('../routes/mapLocationsRouter')(mapLocations); +const timeOffRequestRouter = require('../routes/timeOffRequestRouter')(timeOffRequest); // bm dashboard const bmLoginRouter = require('../routes/bmdashboard/bmLoginRouter')(); @@ -123,4 +125,5 @@ module.exports = function (app) { app.use('/api/bm', bmInventoryTypeRouter); app.use('/api/bm', bmToolRouter); app.use('/api/bm', bmConsumablesRouter); + app.use('/api', timeOffRequestRouter); }; diff --git a/src/utilities/logPermissionChangeByAccount.js b/src/utilities/logPermissionChangeByAccount.js index dac2a4016..0d3f72f76 100644 --- a/src/utilities/logPermissionChangeByAccount.js +++ b/src/utilities/logPermissionChangeByAccount.js @@ -1,17 +1,15 @@ -const moment = require("moment-timezone"); -const PermissionChangeLog = require("../models/permissionChangeLog") +const moment = require('moment-timezone'); +const PermissionChangeLog = require('../models/permissionChangeLog'); // Middleware function const changedPermissionsLogger = async (req, res, next) => { - await logPermissionChangeByAccount(req.body) + await logPermissionChangeByAccount(req.body); next(); }; // Helper function finds the latest log related to the permission -const findLatestRelatedLog = (roleId) => { - - return new Promise((resolve, reject) => { - PermissionChangeLog.findOne({ roleId: roleId }) +const findLatestRelatedLog = roleId => new Promise((resolve, reject) => { + PermissionChangeLog.findOne({ roleId }) .sort({ logDateTime: -1 }) .exec((err, document) => { if (err) { @@ -21,48 +19,48 @@ const findLatestRelatedLog = (roleId) => { } resolve(document); }); - }) -} + }); // Function saves logs to hgnData_dev.permissionChangeLogs collection const logPermissionChangeByAccount = async (requestBody) => { - const { roleId, roleName, permissions, requestor, role, email } = requestBody - const dateTime = moment().tz("America/Los_Angeles").format(); - + const { + roleId, roleName, permissions, requestor, role, email, +} = requestBody; + const dateTime = moment().tz('America/Los_Angeles').format(); + try { - let permissionsAdded = [] - let permissionsRemoved = [] + let permissionsAdded = []; + let permissionsRemoved = []; // Find the latest log related to permission - const document = await findLatestRelatedLog(roleId) + const document = await findLatestRelatedLog(roleId); if (document) { - permissionsRemoved = document.permissions.filter(item => !(permissions.includes(item))) - permissionsAdded = permissions.filter(item => !(document.permissions.includes(item))) + permissionsRemoved = document.permissions.filter(item => !(permissions.includes(item))); + permissionsAdded = permissions.filter(item => !(document.permissions.includes(item))); } else { // else this is the first permissions change log for this particular role - permissionsAdded = permissions + permissionsAdded = permissions; } - + const logEntry = new PermissionChangeLog({ logDateTime: dateTime, - roleId: roleId, - roleName: roleName, - permissions: permissions, - permissionsAdded: permissionsAdded, - permissionsRemoved: permissionsRemoved, + roleId, + roleName, + permissions, + permissionsAdded, + permissionsRemoved, requestorId: requestor.requestorId, requestorRole: role, requestorEmail: email, - }) + }); - await logEntry.save() - - } catch (error) { + await logEntry.save(); + } catch (error) { console.error('Error logging permission change:', error); res.status(500).json({ error: 'Failed to log permission change' }); } -} +}; module.exports = changedPermissionsLogger;