diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index c6fee8ab4..4816bd1e2 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -17,9 +17,10 @@ To test this backend PR you need to checkout the #XXX frontend PR. ## How to test: 1. check into current branch 2. do `npm install` and `...` to run this PR locally -3. log as admin user -4. go to dashboard→ Tasks→ task→… -5. verify function “A” (feel free to include screenshot here) +3. Clear site data/cache +4. log as admin user +5. go to dashboard→ Tasks→ task→… +6. verify function “A” (feel free to include screenshot here) ## Screenshots or videos of changes: diff --git a/package-lock.json b/package-lock.json index f7efc3319..0c8f3ee0b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5806,6 +5806,11 @@ "moment": ">= 2.9.0" } }, + "mongo-round": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mongo-round/-/mongo-round-1.0.0.tgz", + "integrity": "sha512-lwvLJv827Uks+3HnTOt1I/Qr78Avke3du1oMaFqFpTwtRKtOposNOKkfpGXQN4ZGpRN3XAS8fEppIJ4TUj0xQw==" + }, "mongodb": { "version": "3.7.3", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-3.7.3.tgz", diff --git a/src/controllers/badgeController.js b/src/controllers/badgeController.js index 366c9323e..5dd2113a6 100644 --- a/src/controllers/badgeController.js +++ b/src/controllers/badgeController.js @@ -1,12 +1,14 @@ +const moment = require('moment-timezone'); const mongoose = require('mongoose'); const UserProfile = require('../models/userProfile'); const { hasPermission } = require('../utilities/permissions'); const escapeRegex = require('../utilities/escapeRegex'); const cache = require('../utilities/nodeCache')(); +const logger = require('../startup/logger'); const badgeController = function (Badge) { const getAllBadges = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'seeBadges')) { + if (!(await hasPermission(req.body.requestor, 'seeBadges'))) { res.status(403).send('You are not authorized to view all badge data.'); return; } @@ -14,10 +16,11 @@ const badgeController = function (Badge) { Badge.find( {}, 'badgeName type multiple weeks months totalHrs people imageUrl category project ranking description showReport', - ).populate({ - path: 'project', - select: '_id projectName', - }) + ) + .populate({ + path: 'project', + select: '_id projectName', + }) .sort({ ranking: 1, badgeName: 1, @@ -26,8 +29,32 @@ const badgeController = function (Badge) { .catch(error => res.status(404).send(error)); }; + /** + * Updated Date: 12/06/2023 + * Updated By: Shengwei + * Function added: + * - Added data validation for earned date and badge count mismatch. + * - Added fillEarnedDateToMatchCount function to resolve earned date and badge count mismatch. + * - Refactored data validation for duplicate badge id. + * - Added data validation for badge count should greater than 0. + * - Added formatDate function to format date to MMM-DD-YY. + */ + + const formatDate = () => { + const currentDate = new Date(Date.now()); + return moment(currentDate).tz('America/Los_Angeles').format('MMM-DD-YY'); + }; + + const fillEarnedDateToMatchCount = (earnedDate, count) => { + const result = [...earnedDate]; + while (result.length < count) { + result.push(formatDate()); + } + return result; + }; + const assignBadges = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'assignBadges')) { + if (!(await hasPermission(req.body.requestor, 'assignBadges'))) { res.status(403).send('You are not authorized to assign badges.'); return; } @@ -39,63 +66,100 @@ const badgeController = function (Badge) { res.status(400).send('Can not find the user to be assigned.'); return; } - const grouped = req.body.badgeCollection.reduce((groupd, item) => { - const propertyValue = item.badge; - groupd[propertyValue] = (groupd[propertyValue] || 0) + 1; - return groupd; - }, {}); - const result = Object.keys(grouped).every(bdge => grouped[bdge] <= 1); - if (result) { - record.badgeCollection = req.body.badgeCollection; - - if (cache.hasCache(`user-${userToBeAssigned}`)) cache.removeCache(`user-${userToBeAssigned}`); - - record.save() - .then(results => res.status(201).send(results._id)) - .catch(errors => res.status(500).send(errors)); - } else { - res.status(500).send('Duplicate badges sent in.'); + const badgeCounts = {}; + // This line is using the forEach function to group badges in the badgeCollection + // array in the request body. + // Validation: No duplicate badge id; + try { + req.body.badgeCollection.forEach((element) => { + if (badgeCounts[element.badge]) { + throw new Error('Duplicate badges sent in.'); + // res.status(500).send('Duplicate badges sent in.'); + // return; + } + badgeCounts[element.badge] = element.count; + // Validation: count should be greater than 0 + if (element.count < 1) { + throw new Error('Badge count should be greater than 0.'); + } + if (element.count !== element.earnedDate.length) { + element.earnedDate = fillEarnedDateToMatchCount( + element.earnedDate, + element.count, + ); + element.lastModified = Date.now(); + logger.logInfo( + `Badge count and earned dates mismatched found. ${Date.now()} was generated for user ${userToBeAssigned}. Badge record ID ${ + element._id + }; Badge Type ID ${element.badge}`, + ); + } + }); + } catch (err) { + res.status(500).send(`Internal Error: Badge Collection. ${ err.message}`); + return; + } + record.badgeCollection = req.body.badgeCollection; + + if (cache.hasCache(`user-${userToBeAssigned}`)) { + cache.removeCache(`user-${userToBeAssigned}`); } + // Save Updated User Profile + record + .save() + .then(results => res.status(201).send(results._id)) + .catch((err) => { + logger.logException(err); + res.status(500).send('Internal Error: Unable to save the record.'); + }); }); }; const postBadge = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'createBadges')) { - res.status(403).send({ error: 'You are not authorized to create new badges.' }); + if (!(await hasPermission(req.body.requestor, 'createBadges'))) { + res + .status(403) + .send({ error: 'You are not authorized to create new badges.' }); return; } - Badge.find({ badgeName: { $regex: escapeRegex(req.body.badgeName), $options: 'i' } }) - .then((result) => { - if (result.length > 0) { - res.status(400).send({ error: `Another badge with name ${result[0].badgeName} already exists. Sorry, but badge names should be like snowflakes, no two should be the same. Please choose a different name for this badge so it can be proudly unique.` }); - return; - } - const badge = new Badge(); - - badge.badgeName = req.body.badgeName; - badge.category = req.body.category; - badge.type = req.body.type; - badge.multiple = req.body.multiple; - badge.totalHrs = req.body.totalHrs; - badge.weeks = req.body.weeks; - badge.months = req.body.months; - badge.people = req.body.people; - badge.project = req.body.project; - badge.imageUrl = req.body.imageUrl; - badge.ranking = req.body.ranking; - badge.description = req.body.description; - badge.showReport = req.body.showReport; - - badge.save() - .then(results => res.status(201).send(results)) - .catch(errors => res.status(500).send(errors)); - }); + Badge.find({ + badgeName: { $regex: escapeRegex(req.body.badgeName), $options: 'i' }, + }).then((result) => { + if (result.length > 0) { + res.status(400).send({ + error: `Another badge with name ${result[0].badgeName} already exists. Sorry, but badge names should be like snowflakes, no two should be the same. Please choose a different name for this badge so it can be proudly unique.`, + }); + return; + } + const badge = new Badge(); + + badge.badgeName = req.body.badgeName; + badge.category = req.body.category; + badge.type = req.body.type; + badge.multiple = req.body.multiple; + badge.totalHrs = req.body.totalHrs; + badge.weeks = req.body.weeks; + badge.months = req.body.months; + badge.people = req.body.people; + badge.project = req.body.project; + badge.imageUrl = req.body.imageUrl; + badge.ranking = req.body.ranking; + badge.description = req.body.description; + badge.showReport = req.body.showReport; + + badge + .save() + .then(results => res.status(201).send(results)) + .catch(errors => res.status(500).send(errors)); + }); }; const deleteBadge = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'deleteBadges')) { - res.status(403).send({ error: 'You are not authorized to delete badges.' }); + if (!(await hasPermission(req.body.requestor, 'deleteBadges'))) { + res + .status(403) + .send({ error: 'You are not authorized to delete badges.' }); return; } const { badgeId } = req.params; @@ -104,19 +168,31 @@ const badgeController = function (Badge) { res.status(400).send({ error: 'No valid records found' }); return; } - const removeBadgeFromProfile = UserProfile.updateMany({}, { $pull: { badgeCollection: { badge: record._id } } }).exec(); + const removeBadgeFromProfile = UserProfile.updateMany( + {}, + { $pull: { badgeCollection: { badge: record._id } } }, + ).exec(); const deleteRecord = record.remove(); Promise.all([removeBadgeFromProfile, deleteRecord]) - .then(res.status(200).send({ message: 'Badge successfully deleted and user profiles updated' })) - .catch((errors) => { res.status(500).send(errors); }); - }) - .catch((error) => { res.status(500).send(error); }); + .then( + res.status(200).send({ + message: 'Badge successfully deleted and user profiles updated', + }), + ) + .catch((errors) => { + res.status(500).send(errors); + }); + }).catch((error) => { + res.status(500).send(error); + }); }; const putBadge = async function (req, res) { - if (!await hasPermission(req.body.requestor, 'updateBadges')) { - res.status(403).send({ error: 'You are not authorized to update badges.' }); + if (!(await hasPermission(req.body.requestor, 'updateBadges'))) { + res + .status(403) + .send({ error: 'You are not authorized to update badges.' }); return; } const { badgeId } = req.params; @@ -129,7 +205,6 @@ const badgeController = function (Badge) { // store onto Azure and return url } - const data = { badgeName: req.body.name || req.body.badgeName, description: req.body.description, diff --git a/src/controllers/bmdashboard/bmMaterialsController.js b/src/controllers/bmdashboard/bmMaterialsController.js index a8090ebd9..911ca0b55 100644 --- a/src/controllers/bmdashboard/bmMaterialsController.js +++ b/src/controllers/bmdashboard/bmMaterialsController.js @@ -1,27 +1,17 @@ const mongoose = require('mongoose'); -// use in bmPurchaseMaterials auth check (see below) -// const buildingProject = require('../../models/bmdashboard/buildingProject'); - -const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { +const bmMaterialsController = function (ItemMaterial,BuildingMaterial) { const bmMaterialsList = async function _matsList(req, res) { try { - ItemMaterial.find() + BuildingMaterial.find() .populate([ { path: 'project', - select: '_id projectName', - }, - { - path: 'inventoryItemType', - select: '_id name uom totalStock totalAvailable', + select: '_id name', }, { - path: 'usageRecord', - populate: { - path: 'createdBy', - select: '_id firstName lastName', - }, + path: 'itemType', + select: '_id name unit', }, { path: 'updateRecord', @@ -33,7 +23,7 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { { path: 'purchaseRecord', populate: { - path: 'createdBy', + path: 'requestedBy', select: '_id firstName lastName', }, }, @@ -99,10 +89,132 @@ const bmMaterialsController = function (ItemMaterial, BuildingMaterial) { res.status(500).send(error); } }; + + const bmPostMaterialUpdateRecord = function (req, res) { + let payload = req.body; + let quantityUsed = +req.body.quantityUsed; + let quantityWasted = +req.body.quantityWasted; + let material = req.body.material; + if(payload.QtyUsedLogUnit=='percent' && quantityWasted>=0) + { + quantityUsed = +((+quantityUsed / 100) * material.stockAvailable).toFixed(4); + } + if(payload.QtyWastedLogUnit=='percent' && quantityUsed>=0) + { + quantityWasted = +((+quantityWasted / 100) * material.stockAvailable).toFixed(4); + } + + if(quantityUsed>material.stockAvailable || quantityWasted>material.stockAvailable || (quantityUsed+quantityWasted)>material.stockAvailable) + { + res.status(500).send('Please check the used and wasted stock values. Either individual values or their sum exceeds the total stock available.') + } + else + { + let newStockUsed = +material.stockUsed + parseFloat(quantityUsed); + let newStockWasted = +material.stockWasted + parseFloat(quantityWasted); + let newAvailable = +material.stockAvailable - parseFloat(quantityUsed) - parseFloat(quantityWasted); + newStockUsed = parseFloat(newStockUsed.toFixed(4)); + newStockWasted = parseFloat(newStockWasted.toFixed(4)); + newAvailable = parseFloat(newAvailable.toFixed(4)); + BuildingMaterial.updateOne( + { _id: req.body.material._id }, + + { + $set: { + 'stockUsed': newStockUsed, + 'stockWasted': newStockWasted, + 'stockAvailable': newAvailable + }, + $push: { + updateRecord: { + date: req.body.date, + createdBy: req.body.requestor.requestorId, + quantityUsed: quantityUsed, + quantityWasted: quantityWasted + }, + } + } + + ) + .then(results => {res.status(200).send(results)}) + .catch(error => res.status(500).send({'message':error})); + } + }; + + const bmPostMaterialUpdateBulk = function (req, res) { + const materialUpdates= req.body.upadateMaterials; + let errorFlag = false; + const updateRecordsToBeAdded = []; + for(let i=0;i=0) + { + quantityUsed = +((+quantityUsed / 100) * material.stockAvailable).toFixed(4); + } + if(payload.QtyWastedLogUnit=='percent' && quantityUsed>=0) + { + quantityWasted = +((+quantityWasted / 100) * material.stockAvailable).toFixed(4); + } + + let newStockUsed = +material.stockUsed + parseFloat(quantityUsed); + let newStockWasted = +material.stockWasted + parseFloat(quantityWasted); + let newAvailable = +material.stockAvailable - parseFloat(quantityUsed) - parseFloat(quantityWasted); + newStockUsed = parseFloat(newStockUsed.toFixed(4)); + newStockWasted = parseFloat(newStockWasted.toFixed(4)); + newAvailable = parseFloat(newAvailable.toFixed(4)); + if(newAvailable<0) + { + errorFlag = true; + break; + } + updateRecordsToBeAdded.push ({ + updateId: material._id, + set: { + 'stockUsed': newStockUsed, + 'stockWasted': newStockWasted, + 'stockAvailable': newAvailable + }, + updateValue: { + createdBy: req.body.requestor.requestorId, + quantityUsed: quantityUsed, + quantityWasted: quantityWasted, + date: req.body.date, + }}); + + } + + try { + if(errorFlag) + { + res.status(500).send('Stock quantities submitted seems to be invalid') + return; + } + const updatePromises = updateRecordsToBeAdded.map(updateItem => BuildingMaterial.updateOne( + { _id: updateItem.updateId }, + { + $set : updateItem.set, + $push: { updateRecord: updateItem.updateValue } + }, + ).exec()); + Promise.all(updatePromises) + .then((results) => { + res.status(200).send({ result: `Successfully posted log for ${results.length} Material records.` }); + }) + .catch(error => res.status(500).send(error)); + } catch (err) { + res.json(err); + } + }; return { bmMaterialsList, - bmPurchaseMaterials, - }; + bmPostMaterialUpdateRecord, + bmPostMaterialUpdateBulk, + bmPurchaseMaterials +}; }; module.exports = bmMaterialsController; diff --git a/src/controllers/bmdashboard/bmProjectController.js b/src/controllers/bmdashboard/bmProjectController.js index 929aba4ba..b6b3e4e18 100644 --- a/src/controllers/bmdashboard/bmProjectController.js +++ b/src/controllers/bmdashboard/bmProjectController.js @@ -1,3 +1,4 @@ +/* eslint-disable prefer-destructuring */ // TODO: uncomment when executing auth checks // const jwt = require('jsonwebtoken'); // const config = require('../../config'); @@ -12,26 +13,73 @@ const bmMProjectController = function (BuildingProject) { // const token = req.headers.authorization; // const { userid } = jwt.verify(token, JWT_SECRET); try { - const projectData = await BuildingProject - // TODO: uncomment this line to filter by buildingManager field - // .find({ buildingManager: userid }) - .find() - .populate([ - { - path: 'buildingManager', - select: '_id firstName lastName email', + BuildingProject.aggregate([ + { + $match: { isActive: true }, + }, + { + $lookup: { + from: 'userProfiles', + let: { id: '$buildingManager' }, + pipeline: [ + { $match: { $expr: { $eq: ['$_id', '$$id'] } } }, + { $project: { firstName: 1, lastName: 1, email: 1 } }, + ], + as: 'buildingManager', }, - { - path: 'team', - select: '_id firstName lastName email', + }, + { $unwind: '$buildingManager' }, + { + $lookup: { + from: 'buildingMaterials', + let: { id: '$_id' }, + pipeline: [ + { $match: { $expr: { $eq: ['$project', '$$id'] } } }, + { $project: { updateRecord: 0, project: 0 } }, + { + $lookup: { + from: 'buildingInventoryTypes', + localField: 'itemType', + foreignField: '_id', + as: 'itemType', + }, + }, + { + $unwind: '$itemType', + }, + ], + as: 'materials', }, - ]) - .exec() - .then(result => result) - .catch(error => res.status(500).send(error)); - res.status(200).send(projectData); + }, + { + $project: { + name: 1, + isActive: 1, + template: 1, + location: 1, + dateCreated: 1, + buildingManager: 1, + teams: 1, + members: 1, + materials: 1, + hoursWorked: { $sum: '$members.hours' }, + // cost values can be calculated once a process for purchasing inventory is created + totalMaterialsCost: { $sum: 1500 }, + totalEquipmentCost: { $sum: 3000 }, + }, + }, + ]) + .then((results) => { + results.forEach((proj) => { + proj.mostMaterialWaste = proj.materials.sort((a, b) => b.stockWasted - a.stockWasted)[0]; + proj.leastMaterialAvailable = proj.materials.sort((a, b) => a.stockAvailable - b.stockAvailable)[0]; + proj.mostMaterialBought = proj.materials.sort((a, b) => b.stockBought - a.stockBought)[0]; + }); + res.status(200).send(results); + }) + .catch(error => res.status(500).send(error)); } catch (err) { - res.json(err); + res.status(500).send(err); } }; diff --git a/src/controllers/dashBoardController.js b/src/controllers/dashBoardController.js index ecfc61d8b..68bd62142 100644 --- a/src/controllers/dashBoardController.js +++ b/src/controllers/dashBoardController.js @@ -296,4 +296,4 @@ const dashboardcontroller = function () { }; }; -module.exports = dashboardcontroller; +module.exports = dashboardcontroller; \ No newline at end of file diff --git a/src/controllers/isEmailExistsController.js b/src/controllers/isEmailExistsController.js index 2c41efc33..f6009a3c5 100644 --- a/src/controllers/isEmailExistsController.js +++ b/src/controllers/isEmailExistsController.js @@ -1,25 +1,23 @@ const UserProfile = require('../models/userProfile'); const isEmailExistsController = function () { - const isEmailExists = async function (req, res) { - - try { - const userProfile = await UserProfile.findOne({ email: req.params.email }).lean().exec() + try { + const userProfile = await UserProfile.findOne({ email: req.params.email }).lean().exec(); if (userProfile) { - res.status(200).send(`Email, ${userProfile.email}, found.`) + res.status(200).send(`Email, ${userProfile.email}, found.`); } else { - res.status(403).send(`Email, ${req.params.email}, not found.`) + res.status(403).send(`Email, ${req.params.email}, not found.`); } } catch (err) { - console.log(err) + console.log(err); } - } + }; return { - isEmailExists - } -} + isEmailExists, + }; +}; -module.exports = isEmailExistsController +module.exports = isEmailExistsController; diff --git a/src/controllers/ownerMessageController.js b/src/controllers/ownerMessageController.js index b4a00431b..1b2c30205 100644 --- a/src/controllers/ownerMessageController.js +++ b/src/controllers/ownerMessageController.js @@ -1,63 +1,63 @@ const ownerMessageController = function (OwnerMessage) { - const postOwnerMessage = function (req, res) { - if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to create messages!'); + const getOwnerMessage = async function (req, res) { + try { + const results = await OwnerMessage.find({}); + if (results.length === 0) { // first time initialization + const ownerMessage = new OwnerMessage(); + await ownerMessage.save(); + res.status(200).send({ ownerMessage }); + } else { + res.status(200).send({ ownerMessage: results[0] }); + } + } catch (error) { + res.status(404).send(error); } - const ownerMessage = new OwnerMessage(); - ownerMessage.message = req.body.newMessage; - ownerMessage.save().then(() => res.status(201).json({ - _serverMessage: 'Message succesfuly created!', - ownerMessage, - })).catch(err => res.status(500).send({ err })); - }; - - const getOwnerMessage = function (req, res) { - OwnerMessage.find() - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); }; - const deleteOwnerMessage = function (req, res) { + const updateOwnerMessage = async function (req, res) { if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to delete messages!'); + res.status(403).send('You are not authorized to create messages!'); + } + const { isStandard, newMessage } = req.body; + try { + const results = await OwnerMessage.find({}); + const ownerMessage = results[0]; + if (isStandard) { + ownerMessage.standardMessage = newMessage; + ownerMessage.message = ''; + } else { + ownerMessage.message = newMessage; + } + await ownerMessage.save(); + const { standardMessage, message } = ownerMessage; + res.status(201).send({ + _serverMessage: 'Update successfully!', + ownerMessage: { standardMessage, message }, + }); + } catch (error) { + res.status(500).send(error); } - OwnerMessage.deleteMany({}) - .then((result) => { - result - .then(res.status(200).send({ _serverMessage: 'Message deleted!' })) - .catch((error) => { - res.status(400).send(error); - }); - }) - .catch((error) => { - res.status(400).send(error); - }); }; - const updateOwnerMessage = function (req, res) { + const deleteOwnerMessage = async function (req, res) { if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to update messages!'); + res.status(403).send('You are not authorized to delete messages!'); + } + try { + const results = await OwnerMessage.find({}); + const ownerMessage = results[0]; + ownerMessage.message = ''; + await ownerMessage.save(); + res.status(200).send({ _serverMessage: 'Delete successfully!', ownerMessage }); + } catch (error) { + res.status(500).send(error); } - const { id } = req.params; - - return OwnerMessage.findById(id, (error, ownerMessage) => { - if (error || ownerMessage === null) { - res.status(400).send('No ownerMessage found'); - return; - } - - ownerMessage.message = req.body.newMessage; - ownerMessage.save() - .then(results => res.status(201).send(results)) - .catch(errors => res.status(400).send(errors)); - }); }; return { - postOwnerMessage, getOwnerMessage, - deleteOwnerMessage, updateOwnerMessage, + deleteOwnerMessage, }; }; diff --git a/src/controllers/ownerStandardMessageController.js b/src/controllers/ownerStandardMessageController.js deleted file mode 100644 index efd933888..000000000 --- a/src/controllers/ownerStandardMessageController.js +++ /dev/null @@ -1,64 +0,0 @@ -const ownerStandardMessageController = function (OwnerStandardMessage) { - const postOwnerStandardMessage = function (req, res) { - if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to create messages!'); - } - const ownerStandardMessage = new OwnerStandardMessage(); - ownerStandardMessage.message = req.body.newStandardMessage; - ownerStandardMessage.save().then(() => res.status(201).json({ - _serverMessage: 'Message succesfuly created!', - ownerStandardMessage, - })).catch(err => res.status(500).send({ err })); - }; - - const getOwnerStandardMessage = function (req, res) { - OwnerStandardMessage.find() - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); - }; - - const deleteOwnerStandardMessage = function (req, res) { - if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to delete messages!'); - } - OwnerStandardMessage.deleteMany({}) - .then((result) => { - result - .then(res.status(200).send({ _serverMessage: 'Standard Message deleted!' })) - .catch((error) => { - res.status(400).send(error); - }); - }) - .catch((error) => { - res.status(400).send(error); - }); - }; - - const updateOwnerStandardMessage = function (req, res) { - if (req.body.requestor.role !== 'Owner') { - res.status(403).send('You are not authorized to update messages!'); - } - const { id } = req.params; - - return OwnerStandardMessage.findById(id, (error, ownerStandardMessage) => { - if (error || ownerStandardMessage === null) { - res.status(400).send('No ownerStandardMessage found'); - return; - } - - ownerStandardMessage.message = req.body.newStandardMessage; - ownerStandardMessage.save() - .then(results => res.status(201).send(results)) - .catch(errors => res.status(400).send(errors)); - }); - }; - - return { - postOwnerStandardMessage, - getOwnerStandardMessage, - deleteOwnerStandardMessage, - updateOwnerStandardMessage, - }; -}; - -module.exports = ownerStandardMessageController; diff --git a/src/controllers/profileInitialSetupController.js b/src/controllers/profileInitialSetupController.js index 2e64b5e98..315ed01bb 100644 --- a/src/controllers/profileInitialSetupController.js +++ b/src/controllers/profileInitialSetupController.js @@ -1,9 +1,9 @@ -const mongoose = require("mongoose"); -const { v4: uuidv4 } = require("uuid"); -const moment = require("moment-timezone"); -const jwt = require("jsonwebtoken"); -const emailSender = require("../utilities/emailSender"); -const config = require("../config"); +const mongoose = require('mongoose'); +const { v4: uuidv4 } = require('uuid'); +const moment = require('moment-timezone'); +const jwt = require('jsonwebtoken'); +const emailSender = require('../utilities/emailSender'); +const config = require('../config'); const cache = require('../utilities/nodeCache')(); // returns the email body that includes the setup link for the recipient. @@ -96,7 +96,7 @@ const sendEmailWithAcknowledgment = (email, subject, message) => { const profileInitialSetupController = function ( ProfileInitialSetupToken, userProfile, - Project + Project, ) { const { JWT_SECRET } = config; @@ -108,16 +108,16 @@ const profileInitialSetupController = function ( - Generates a link using the token and emails it to the recipient. */ const getSetupToken = async (req, res) => { - let { email, baseUrl,weeklyCommittedHours } = req.body; + let { email, baseUrl, weeklyCommittedHours } = req.body; email = email.toLowerCase(); const token = uuidv4(); - const expiration = moment().tz("America/Los_Angeles").add(1, "week"); + const expiration = moment().tz('America/Los_Angeles').add(1, 'week'); try { const existingEmail = await userProfile.findOne({ - email: email, + email, }); if (existingEmail) { - res.status(400).send("email already in use"); + res.status(400).send('email already in use'); } else { await ProfileInitialSetupToken.findOneAndDelete({ email }); @@ -151,7 +151,7 @@ const profileInitialSetupController = function ( */ const validateSetupToken = async (req, res) => { const { token } = req.body; - const currentMoment = moment.tz("America/Los_Angeles"); + const currentMoment = moment.tz('America/Los_Angeles'); try { const foundToken = await ProfileInitialSetupToken.findOne({ token }); @@ -161,10 +161,10 @@ const profileInitialSetupController = function ( if (expirationMoment.isAfter(currentMoment)) { res.status(200).send(foundToken); } else { - res.status(400).send("Invalid token"); + res.status(400).send('Invalid token'); } } else { - res.status(404).send("Token not found"); + res.status(404).send('Token not found'); } } catch (error) { res.status(500).send(`Error finding token: ${error}`); @@ -182,31 +182,30 @@ const profileInitialSetupController = function ( */ const setUpNewUser = async (req, res) => { const { token } = req.body; - const currentMoment = moment.tz("America/Los_Angeles"); + const currentMoment = moment.tz('America/Los_Angeles'); try { const foundToken = await ProfileInitialSetupToken.findOne({ token }); const existingEmail = await userProfile.findOne({ email: foundToken.email, }); if (existingEmail) { - res.status(400).send("email already in use"); - } else { - if (foundToken) { + res.status(400).send('email already in use'); + } else if (foundToken) { const expirationMoment = moment(foundToken.expiration); if (expirationMoment.isAfter(currentMoment)) { const defaultProject = await Project.findOne({ - projectName: "Orientation and Initial Setup", + projectName: 'Orientation and Initial Setup', }); const newUser = new userProfile(); newUser.password = req.body.password; - newUser.role = "Volunteer"; + newUser.role = 'Volunteer'; newUser.firstName = req.body.firstName; newUser.lastName = req.body.lastName; newUser.jobTitle = req.body.jobTitle; newUser.phoneNumber = req.body.phoneNumber; - newUser.bio = ""; + newUser.bio = ''; newUser.weeklycommittedHours = foundToken.weeklyCommittedHours; newUser.weeklycommittedHoursHistory = [ { @@ -220,32 +219,31 @@ const profileInitialSetupController = function ( newUser.projects = Array.from(new Set([defaultProject])); newUser.createdDate = Date.now(); newUser.email = req.body.email; - newUser.weeklySummaries = [{ summary: "" }]; + newUser.weeklySummaries = [{ summary: '' }]; newUser.weeklySummariesCount = 0; - newUser.weeklySummaryOption = "Required"; - newUser.mediaUrl = ""; + newUser.weeklySummaryOption = 'Required'; + newUser.mediaUrl = ''; newUser.collaborationPreference = req.body.collaborationPreference; - newUser.timeZone = req.body.timeZone || "America/Los_Angeles"; + newUser.timeZone = req.body.timeZone || 'America/Los_Angeles'; newUser.location = req.body.location; newUser.permissions = { frontPermissions: [], - backPermissions: [] - } - newUser.bioPosted = "default"; + backPermissions: [], + }; + newUser.bioPosted = 'default'; newUser.privacySettings.email = req.body.privacySettings.email; - newUser.privacySettings.phoneNumber = - req.body.privacySettings.phoneNumber; - newUser.teamCode = ""; + newUser.privacySettings.phoneNumber = req.body.privacySettings.phoneNumber; + newUser.teamCode = ''; newUser.isFirstTimelog = true; const savedUser = await newUser.save(); emailSender( - process.env.MANAGER_EMAIL || "jae@onecommunityglobal.org", // "jae@onecommunityglobal.org" + process.env.MANAGER_EMAIL || 'jae@onecommunityglobal.org', // "jae@onecommunityglobal.org" `NEW USER REGISTERED: ${savedUser.firstName} ${savedUser.lastName}`, informManagerMessage(savedUser), null, - null + null, ); await ProfileInitialSetupToken.findByIdAndDelete(foundToken._id); @@ -255,14 +253,14 @@ const profileInitialSetupController = function ( permissions: savedUser.permissions, expiryTimestamp: moment().add( config.TOKEN.Lifetime, - config.TOKEN.Units + config.TOKEN.Units, ), }; const token = jwt.sign(jwtPayload, JWT_SECRET); res.send({ token }).status(200); - + const NewUserCache = { permissions: savedUser.permissions, isActive: true, @@ -275,18 +273,15 @@ const profileInitialSetupController = function ( email: savedUser.email, }; - const allUserCache = JSON.parse(cache.getCache("allusers")); + const allUserCache = JSON.parse(cache.getCache('allusers')); allUserCache.push(NewUserCache); - cache.setCache("allusers", JSON.stringify(allUserCache)); - - + cache.setCache('allusers', JSON.stringify(allUserCache)); } else { - res.status(400).send("Token is expired"); + res.status(400).send('Token is expired'); } } else { - res.status(400).send("Invalid token"); + res.status(400).send('Invalid token'); } - } } catch (error) { res.status(500).send(`Error: ${error}`); } @@ -298,17 +293,15 @@ const profileInitialSetupController = function ( - sends the API Key as response */ const getTimeZoneAPIKeyByToken = async (req, res) => { - const token = req.body.token; + const { token } = req.body; const premiumKey = process.env.TIMEZONE_PREMIUM_KEY; const foundToken = await ProfileInitialSetupToken.findOne({ token }); if (foundToken) { res.status(200).send({ userAPIKey: premiumKey }); - return; } else { - res.status(403).send("Unauthorized Request"); - return; + res.status(403).send('Unauthorized Request'); } }; diff --git a/src/controllers/rolePresetsController.js b/src/controllers/rolePresetsController.js index 3642627f7..3a7dc18c8 100644 --- a/src/controllers/rolePresetsController.js +++ b/src/controllers/rolePresetsController.js @@ -2,7 +2,7 @@ const { hasPermission } = require('../utilities/permissions'); const rolePresetsController = function (Preset) { const getPresetsByRole = async function (req, res) { - if (!await hasPermission(req.body.requestor.role, 'putRole')) { + if (!await hasPermission(req.body.requestor, 'putRole')) { res.status(403).send('You are not authorized to make changes to roles.'); return; } @@ -14,7 +14,7 @@ const rolePresetsController = function (Preset) { }; const createNewPreset = async function (req, res) { - if (!await hasPermission(req.body.requestor.role, 'putRole')) { + if (!await hasPermission(req.body.requestor, 'putRole')) { res.status(403).send('You are not authorized to make changes to roles.'); return; } @@ -34,7 +34,7 @@ const rolePresetsController = function (Preset) { }; const updatePresetById = async function (req, res) { - if (!await hasPermission(req.body.requestor.role, 'putRole')) { + if (!await hasPermission(req.body.requestor, 'putRole')) { res.status(403).send('You are not authorized to make changes to roles.'); return; } @@ -53,7 +53,7 @@ const rolePresetsController = function (Preset) { }; const deletePresetById = async function (req, res) { - if (!await hasPermission(req.body.requestor.role, 'putRole')) { + if (!await hasPermission(req.body.requestor, 'putRole')) { res.status(403).send('You are not authorized to make changes to roles.'); return; } diff --git a/src/controllers/teamController.js b/src/controllers/teamController.js index 86733daf4..5c8cb5cc2 100644 --- a/src/controllers/teamController.js +++ b/src/controllers/teamController.js @@ -29,16 +29,25 @@ const teamcontroller = function (Team) { } const team = new Team(); - team.teamName = req.body.teamName; - team.isACtive = true; + team.isActive = req.body.isActive; team.createdDatetime = Date.now(); team.modifiedDatetime = Date.now(); - team - .save() - .then(results => res.status(200).send(results)) - .catch(error => res.status(404).send(error)); + // Check if a team with the same name already exists + Team.findOne({ teamName: team.teamName }) + .then((existingTeam) => { + if (existingTeam) { + // If a team with the same name exists, return an error + res.status(400).send({ error: 'A team with this name already exists' }); + } else { + // If no team with the same name exists, save the new team + team.save() + .then(results => res.send(results).status(200)) + .catch(error => res.send(error).status(404)); + } + }) + .catch(error => res.send(error).status(404)); }; const deleteTeam = async function (req, res) { if (!await hasPermission(req.body.requestor, 'deleteTeam')) { diff --git a/src/controllers/timeEntryController.js b/src/controllers/timeEntryController.js index 962f30170..effe61a3b 100644 --- a/src/controllers/timeEntryController.js +++ b/src/controllers/timeEntryController.js @@ -14,6 +14,13 @@ const formatSeconds = function (seconds) { return values.split(':'); }; +const isGeneralTimeEntry = function (type) { + if (type === undefined || type === 'default') { + return true; + } + return false; +}; + /** * * @param {*} firstName First name of the owner of the time entry that was modified @@ -24,9 +31,20 @@ const formatSeconds = function (seconds) { * @param {*} requestor The userProfile object of the person that modified the time entry * @returns {String} */ -const getEditedTimeEntryEmailBody = (firstName, lastName, email, originalTime, finalTime, requestor) => { - const formattedOriginal = moment.utc(originalTime * 1000).format('HH[ hours ]mm[ minutes]'); - const formattedFinal = moment.utc(finalTime * 1000).format('HH[ hours ]mm[ minutes]'); +const getEditedTimeEntryEmailBody = ( + firstName, + lastName, + email, + originalTime, + finalTime, + requestor, +) => { + const formattedOriginal = moment + .utc(originalTime * 1000) + .format('HH[ hours ]mm[ minutes]'); + const formattedFinal = moment + .utc(finalTime * 1000) + .format('HH[ hours ]mm[ minutes]'); return ` A time entry belonging to ${firstName} ${lastName} (${email}) was modified by ${requestor.firstName} ${requestor.lastName} (${requestor.email}). The entry's duration was changed from [${formattedOriginal}] to [${formattedFinal}] @@ -45,18 +63,38 @@ const notifyEditByEmail = async (personId, original, finalTime, final) => { try { const originalTime = original.totalSeconds; const record = await userProfile.findById(personId); - const requestor = (personId !== final.requestor.requestorId) ? await userProfile.findById(final.requestor.requestorId) : record; - const emailBody = getEditedTimeEntryEmailBody(record.firstName, record.lastName, record.email, originalTime, finalTime, requestor); - emailSender('onecommunityglobal@gmail.com', `A Time Entry was Edited for ${record.firstName} ${record.lastName}`, emailBody); + const requestor = personId !== final.requestor.requestorId + ? await userProfile.findById(final.requestor.requestorId) + : record; + const emailBody = getEditedTimeEntryEmailBody( + record.firstName, + record.lastName, + record.email, + originalTime, + finalTime, + requestor, + ); + emailSender( + 'onecommunityglobal@gmail.com', + `A Time Entry was Edited for ${record.firstName} ${record.lastName}`, + emailBody, + ); } catch (error) { - throw new Error(`Failed to send email notification about the modification of time entry belonging to user with id ${personId}`); + throw new Error( + `Failed to send email notification about the modification of time entry belonging to user with id ${personId}`, + ); } }; -const notifyTaskOvertimeEmailBody = async (personId, taskName, estimatedHours, hoursLogged) => { +const notifyTaskOvertimeEmailBody = async ( + personId, + taskName, + estimatedHours, + hoursLogged, +) => { try { - const record = await userProfile.findById(personId); - const text = `Dear ${record.firstName}${record.lastName}, + 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}

Time Estimated : ${estimatedHours}

@@ -64,24 +102,37 @@ const notifyTaskOvertimeEmailBody = async (personId, taskName, estimatedHours, h

Please connect with your manager to explain what happened and submit a new hours estimation for completion.

Thank you,

One Community

`; - emailSender( - record.email, - 'Logged more hours than estimated for a task', - text, - 'onecommunityglobal@gmail.com', - null, + emailSender( + record.email, + 'Logged more hours than estimated for a task', + text, + 'onecommunityglobal@gmail.com', + null, + record.email, + null, ); } catch (error) { - console.log(`Failed to send email notification about the overtime for a task belonging to user with id ${personId}`); + console.log( + `Failed to send email notification about the overtime for a task belonging to user with id ${personId}` + ); } }; const checkTaskOvertime = async (timeentry, record, currentTask) => { try { // send email notification if logged in hours exceeds estiamted hours for a task - if (currentTask.hoursLogged > currentTask.estimatedHours) { notifyTaskOvertimeEmailBody(timeentry.personId.toString(), currentTask.taskName, currentTask.estimatedHours, currentTask.hoursLogged); } + if (currentTask.hoursLogged > currentTask.estimatedHours) { + notifyTaskOvertimeEmailBody( + timeentry.personId.toString(), + currentTask.taskName, + currentTask.estimatedHours, + currentTask.hoursLogged, + ); + } } catch (error) { - console.log(`Failed to find task whose logged-in hours are more than estimated hours ${record.email}`); + console.log( + `Failed to find task whose logged-in hours are more than estimated hours ${record.email}` + ); } }; @@ -90,23 +141,47 @@ const timeEntrycontroller = function (TimeEntry) { const session = await mongoose.startSession(); session.startTransaction(); + const type = req.body.entryType; + const isGeneralEntry = isGeneralTimeEntry(type); + try { if (!req.params.timeEntryId) { - return res.status(400).send({ error: 'ObjectId in request param is not in correct format' }); + return res + .status(400) + .send({ + error: 'ObjectId in request param is not in correct format', + }); } - if (!mongoose.Types.ObjectId.isValid(req.params.timeEntryId) || !mongoose.Types.ObjectId.isValid(req.body.projectId)) { - return res.status(400).send({ error: 'ObjectIds are not correctly formed' }); + if ( + !mongoose.Types.ObjectId.isValid(req.params.timeEntryId) + || ((isGeneralEntry || type === 'project') + && !mongoose.Types.ObjectId.isValid(req.body.projectId) + )) { + return res + .status(400) + .send({ error: 'ObjectIds are not correctly formed' }); } // Get initial timeEntry by timeEntryId const timeEntry = await TimeEntry.findById(req.params.timeEntryId); if (!timeEntry) { - return res.status(400).send({ error: `No valid records found for ${req.params.timeEntryId}` }); + return res + .status(400) + .send({ + error: `No valid records found for ${req.params.timeEntryId}`, + }); } - if (!(await hasPermission(req.body.requestor, 'editTimeEntry') || timeEntry.personId.toString() === req.body.requestor.requestorId.toString())) { + if ( + !( + (await hasPermission(req.body.requestor, 'editTimeEntry')) + || (isGeneralEntry + && timeEntry.personId.toString() + === req.body.requestor.requestorId.toString() + ) + )) { return res.status(403).send({ error: 'Unauthorized request' }); } @@ -115,8 +190,17 @@ const timeEntrycontroller = function (TimeEntry) { const totalSeconds = moment.duration(`${hours}:${minutes}`).asSeconds(); - if (timeEntry.isTangible === true && totalSeconds !== timeEntry.totalSeconds) { - notifyEditByEmail(timeEntry.personId.toString(), timeEntry, totalSeconds, req.body); + if ( + isGeneralEntry + && timeEntry.isTangible === true + && totalSeconds !== timeEntry.totalSeconds + ) { + notifyEditByEmail( + timeEntry.personId.toString(), + timeEntry, + totalSeconds, + req.body, + ); } const initialSeconds = timeEntry.totalSeconds; @@ -131,18 +215,19 @@ const timeEntrycontroller = function (TimeEntry) { 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 (findTask) { + if (isGeneralEntry && findTask) { if (initialIsTangible === true) { - findTask.hoursLogged -= (initialSeconds / 3600); + findTask.hoursLogged -= initialSeconds / 3600; } if (req.body.isTangible === true) { - findTask.hoursLogged += (totalSeconds / 3600); + findTask.hoursLogged += totalSeconds / 3600; } await findTask.save(); @@ -152,63 +237,85 @@ const timeEntrycontroller = function (TimeEntry) { } // Update edit history - if (initialSeconds !== totalSeconds + if ( + (isGeneralEntry || type === 'person') + && initialSeconds !== totalSeconds && timeEntry.isTangible && req.body.requestor.requestorId === timeEntry.personId.toString() - && !await hasPermission(req.body.requestor, 'editTimeEntry') - ) { - const requestor = await userProfile.findById(req.body.requestor.requestorId); + && !(await hasPermission(req.body.requestor, 'editTimeEntry')) + ) { + const requestor = await userProfile.findById( + req.body.requestor.requestorId, + ); requestor.timeEntryEditHistory.push({ date: moment().tz('America/Los_Angeles').toDate(), initialSeconds, newSeconds: totalSeconds, }); - // Issue infraction if edit history contains more than 5 edits in the last year - let totalRecentEdits = 0; - - requestor.timeEntryEditHistory.forEach((edit) => { - if (moment().tz('America/Los_Angeles').diff(edit.date, 'days') <= 365) { - totalRecentEdits += 1; - } - }); + if (isGeneralEntry) { + // Issue infraction if edit history contains more than 5 edits in the last year + let totalRecentEdits = 0; - if (totalRecentEdits >= 5) { - requestor.infringements.push({ - date: moment().tz('America/Los_Angeles'), - description: `${totalRecentEdits} time entry edits in the last calendar year`, + requestor.timeEntryEditHistory.forEach((edit) => { + if ( + moment().tz('America/Los_Angeles').diff(edit.date, 'days') <= 365 + ) { + totalRecentEdits += 1; + } }); - emailSender('onecommunityglobal@gmail.com', `${requestor.firstName} ${requestor.lastName} was issued a blue square for for editing a time entry ${totalRecentEdits} times`, ` -

- ${requestor.firstName} ${requestor.lastName} (${requestor.email}) was issued a blue square for editing their time entries ${totalRecentEdits} times - within the last calendar year. -

-

- This is the ${totalRecentEdits}th edit within the past 365 days. -

- `); - - const emailInfringement = { - date: moment().tz('America/Los_Angeles').format('MMMM-DD-YY'), - description: `You edited your time entries ${totalRecentEdits} times within the last 365 days, exceeding the limit of 4 times per year you can edit them without penalty.`, - }; - - emailSender(requestor.email, 'You\'ve been issued a blue square for editing your time entry', getInfringementEmailBody(requestor.firstName, requestor.lastName, emailInfringement, requestor.infringements.length)); + if (totalRecentEdits >= 5) { + requestor.infringements.push({ + date: moment().tz('America/Los_Angeles'), + description: `${totalRecentEdits} time entry edits in the last calendar year`, + }); + + emailSender( + 'onecommunityglobal@gmail.com', + `${requestor.firstName} ${requestor.lastName} was issued a blue square for for editing a time entry ${totalRecentEdits} times`, + ` +

+ ${requestor.firstName} ${requestor.lastName} (${requestor.email}) was issued a blue square for editing their time entries ${totalRecentEdits} times + within the last calendar year. +

+

+ This is the ${totalRecentEdits}th edit within the past 365 days. +

+ `, + ); + + const emailInfringement = { + date: moment().tz('America/Los_Angeles').format('MMMM-DD-YY'), + description: `You edited your time entries ${totalRecentEdits} times within the last 365 days, exceeding the limit of 4 times per year you can edit them without penalty.`, + }; + + emailSender( + requestor.email, + "You've been issued a blue square for editing your time entry", + getInfringementEmailBody( + requestor.firstName, + requestor.lastName, + emailInfringement, + requestor.infringements.length, + ), + ); + } } await requestor.save(); } - 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 (findTask) { + 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 record = await userProfile.findById( + timeEntry.personId.toString(), + ); const currentTask = await task.findById(req.body.projectId); checkTaskOvertime(timeEntry, record, currentTask); } @@ -229,43 +336,80 @@ const timeEntrycontroller = function (TimeEntry) { } const items = []; records.forEach((element) => { - const timeentry = new TimeEntry(); - timeentry.personId = element.personId; - timeentry.projectId = element.projectId; - timeentry.dateOfWork = element.dateOfWork; - timeentry.timeSpent = moment('1900-01-01 00:00:00') - .add(element.totalSeconds, 'seconds') - .format('HH:mm:ss'); - timeentry.notes = element.notes; - timeentry.isTangible = element.isTangible; - items.push(timeentry); + const isGeneralEntry = isGeneralTimeEntry(element.entryType); + if (isGeneralEntry) { + const timeentry = new TimeEntry(); + timeentry.personId = element.personId; + timeentry.projectId = element.projectId; + timeentry.dateOfWork = element.dateOfWork; + timeentry.timeSpent = moment('1900-01-01 00:00:00') + .add(element.totalSeconds, 'seconds') + .format('HH:mm:ss'); + timeentry.notes = element.notes; + timeentry.isTangible = element.isTangible; + timeentry.entryType = 'default'; + items.push(timeentry); + } }); return res.json(items).status(200); }); }; const postTimeEntry = async function (req, res) { - if ( - !mongoose.Types.ObjectId.isValid(req.body.personId) - || !mongoose.Types.ObjectId.isValid(req.body.projectId) - || !req.body.dateOfWork + const isInvalid = !req.body.dateOfWork || !moment(req.body.dateOfWork).isValid() || !req.body.timeSpent - || !req.body.isTangible - ) { - res.status(400).send({ error: 'Bad request' }); - return; + || !req.body.isTangible; + + const returnErr = (result) => { + result.status(400).send({ error: 'Bad request' }); + }; + + switch (req.body.entryType) { + default: + if ( + !mongoose.Types.ObjectId.isValid(req.body.personId) + || !mongoose.Types.ObjectId.isValid(req.body.projectId) + || isInvalid + ) { + returnErr(res); + } + break; + case 'person': + if ( + !mongoose.Types.ObjectId.isValid(req.body.personId) || isInvalid + ) { + returnErr(res); + } + break; + case 'project': + if ( + !mongoose.Types.ObjectId.isValid(req.body.projectId) || isInvalid + ) { + returnErr(res); + } + break; + case 'team': + if ( + !mongoose.Types.ObjectId.isValid(req.body.teamId) || isInvalid + ) { + returnErr(res); + } + break; } + 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() @@ -274,28 +418,34 @@ const timeEntrycontroller = function (TimeEntry) { .status(200) .send({ message: `Time Entry saved with id as ${results._id}` }); }) - .catch(error => res.status(400).send(error)); + .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); + 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); + if (timeentry.isTangible === true && currentTask) { + try { + currentTask.hoursLogged += timeentry.totalSeconds / 3600; + await currentTask.save(); + } catch (error) { + throw new Error(error); + } } - } - // 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); + // 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); + } } } }; @@ -313,13 +463,18 @@ const timeEntrycontroller = function (TimeEntry) { return; } - const fromdate = moment(req.params.fromdate).tz('America/Los_Angeles').format('YYYY-MM-DD'); - const todate = moment(req.params.todate).tz('America/Los_Angeles').format('YYYY-MM-DD'); + const fromdate = moment(req.params.fromdate) + .tz('America/Los_Angeles') + .format('YYYY-MM-DD'); + const todate = moment(req.params.todate) + .tz('America/Los_Angeles') + .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 }, }, @@ -349,28 +504,16 @@ const timeEntrycontroller = function (TimeEntry) { projectId: 1, lastModifiedDateTime: 1, projectName: { - $arrayElemAt: [ - '$project.projectName', - 0, - ], + $arrayElemAt: ['$project.projectName', 0], }, taskName: { - $arrayElemAt: [ - '$task.taskName', - 0, - ], + $arrayElemAt: ['$task.taskName', 0], }, category: { - $arrayElemAt: [ - '$project.category', - 0, - ], + $arrayElemAt: ['$project.category', 0], }, classification: { - $arrayElemAt: [ - '$task.classification', - 0, - ], + $arrayElemAt: ['$task.classification', 0], }, dateOfWork: 1, hours: { @@ -380,10 +523,7 @@ const timeEntrycontroller = function (TimeEntry) { }, minutes: { $floor: { - $divide: [ - { $mod: ['$totalSeconds', 3600] }, - 60, - ], + $divide: [{ $mod: ['$totalSeconds', 3600] }, 60], }, }, }, @@ -393,9 +533,13 @@ const timeEntrycontroller = function (TimeEntry) { lastModifiedDateTime: -1, }, }, - ]).then((results) => { - res.status(200).send(results); - }).catch(error => res.status(400).send(error)); + ]) + .then((results) => { + res.status(200).send(results); + }) + .catch((error) => { + res.status(400).send(error); + }); }; const getTimeEntriesForUsersList = function (req, res) { @@ -403,6 +547,7 @@ const timeEntrycontroller = function (TimeEntry) { TimeEntry.find( { + entryType: { $in: ['default', null, 'person'] }, personId: { $in: users }, dateOfWork: { $gte: fromDate, $lte: toDate }, }, @@ -429,7 +574,9 @@ const timeEntrycontroller = function (TimeEntry) { }); res.status(200).send(data); }) - .catch(error => res.status(400).send(error)); + .catch((error) => { + res.status(400).send(error); + }); }; const getTimeEntriesForSpecifiedProject = function (req, res) { @@ -457,7 +604,9 @@ const timeEntrycontroller = function (TimeEntry) { .then((results) => { res.status(200).send(results); }) - .catch(error => res.status(400).send(error)); + .catch((error) => { + res.status(400).send(error); + }); }; const deleteTimeEntry = async function (req, res) { @@ -473,18 +622,31 @@ const timeEntrycontroller = function (TimeEntry) { return; } + if (record.entryType === 'project' || record.entryType === 'person' || record.entryType === 'team') { + record + .remove() + .then(() => { + res.status(200).send({ message: 'Successfully deleted' }); + }) + .catch((error) => { + res.status(500).send(error); + }); + return; + } + if ( record.personId.toString() === req.body.requestor.requestorId.toString() - || await hasPermission(req.body.requestor, 'deleteTimeEntry') + || (await hasPermission(req.body.requestor, 'deleteTimeEntry')) ) { // Revert this tangible timeEntry of related task's hoursLogged if (record.isTangible === true) { - task.findById(record.projectId) + 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) if (currentTask) { - currentTask.hoursLogged -= (record.totalSeconds / 3600); + currentTask.hoursLogged -= record.totalSeconds / 3600; currentTask.save(); } }) @@ -510,6 +672,117 @@ const timeEntrycontroller = function (TimeEntry) { }); }; + const getLostTimeEntriesForUserList = function (req, res) { + const { users, fromDate, toDate } = req.body; + + TimeEntry.find( + { + entryType: 'person', + personId: { $in: users }, + dateOfWork: { $gte: fromDate, $lte: toDate }, + }, + ' -createdDateTime', + ) + .populate('personId') + .sort({ lastModifiedDateTime: -1 }) + .then((results) => { + const data = []; + results.forEach((element) => { + const record = {}; + + record._id = element._id; + record.notes = element.notes; + record.isTangible = element.isTangible; + record.personId = element.personId; + record.firstName = element.personId + ? element.personId.firstName + : ''; + record.lastName = element.personId + ? element.personId.lastName + : ''; + record.dateOfWork = element.dateOfWork; + record.entryType = element.entryType; + [record.hours, record.minutes] = formatSeconds(element.totalSeconds); + data.push(record); + }); + res.status(200).send(data); + }) + .catch((error) => { + res.status(400).send(error); + }); + }; + + const getLostTimeEntriesForProjectList = function (req, res) { + const { projects, fromDate, toDate } = req.body; + + TimeEntry.find( + { + entryType: 'project', + projectId: { $in: projects }, + dateOfWork: { $gte: fromDate, $lte: toDate }, + }, + ' -createdDateTime', + ) + .populate('projectId') + .sort({ lastModifiedDateTime: -1 }) + .then((results) => { + const data = []; + results.forEach((element) => { + const record = {}; + record._id = element._id; + record.notes = element.notes; + record.isTangible = element.isTangible; + record.projectId = element.projectId ? element.projectId._id : ''; + record.projectName = element.projectId + ? element.projectId.projectName + : ''; + record.dateOfWork = element.dateOfWork; + record.entryType = element.entryType; + [record.hours, record.minutes] = formatSeconds(element.totalSeconds); + data.push(record); + }); + res.status(200).send(data); + }) + .catch((error) => { + res.status(400).send(error); + }); + }; + + const getLostTimeEntriesForTeamList = function (req, res) { + const { teams, fromDate, toDate } = req.body; + + TimeEntry.find( + { + entryType: 'team', + teamId: { $in: teams }, + dateOfWork: { $gte: fromDate, $lte: toDate }, + }, + ' -createdDateTime', + ) + .populate('teamId') + .sort({ lastModifiedDateTime: -1 }) + .then((results) => { + const data = []; + results.forEach((element) => { + const record = {}; + record._id = element._id; + record.notes = element.notes; + record.isTangible = element.isTangible; + record.teamId = element.teamId ? element.teamId._id : ''; + record.teamName = element.teamId + ? element.teamId.teamName + : ''; + record.dateOfWork = element.dateOfWork; + record.entryType = element.entryType; + [record.hours, record.minutes] = formatSeconds(element.totalSeconds); + data.push(record); + }); + res.status(200).send(data); + }) + .catch((error) => { + res.status(400).send(error); + }); + }; return { getAllTimeEnteries, @@ -520,6 +793,9 @@ const timeEntrycontroller = function (TimeEntry) { deleteTimeEntry, getTimeEntriesForSpecifiedProject, checkTaskOvertime, + getLostTimeEntriesForUserList, + getLostTimeEntriesForProjectList, + getLostTimeEntriesForTeamList, }; }; diff --git a/src/controllers/userProfileController.js b/src/controllers/userProfileController.js index c8e7be13b..1f7e4ab99 100644 --- a/src/controllers/userProfileController.js +++ b/src/controllers/userProfileController.js @@ -36,7 +36,15 @@ async function ValidatePassword(req, res) { return; } // Verify request is authorized by self or adminsitrator - if (!userId === requestor.requestorId && !await hasPermission(req.body.requestor, 'updatePassword')) { + if (userId !== requestor.requestorId && !await hasPermission(req.body.requestor, 'updatePassword')) { + res.status(403).send({ + error: "You are unauthorized to update this user's password", + }); + return; + } + + // Verify request is authorized by self or adminsitrator + if (userId === requestor.requestorId || !await hasPermission(req.body.requestor, 'updatePassword')) { res.status(403).send({ error: "You are unauthorized to update this user's password", }); @@ -60,27 +68,25 @@ const userProfileController = function (UserProfile) { UserProfile.find( {}, - "_id firstName lastName role weeklycommittedHours email permissions isActive reactivationDate createdDate endDate" + '_id firstName lastName role weeklycommittedHours email permissions isActive reactivationDate createdDate endDate', ) .sort({ lastName: 1, }) .then((results) => { if (!results) { - if (cache.getCache("allusers")) { - const getData = JSON.parse(cache.getCache("allusers")); + if (cache.getCache('allusers')) { + const getData = JSON.parse(cache.getCache('allusers')); res.status(200).send(getData); return; - }else{ - res.status(500).send({ error: "User result was invalid" }); - return; } + res.status(500).send({ error: 'User result was invalid' }); + return; } - cache.setCache("allusers", JSON.stringify(results)); + cache.setCache('allusers', JSON.stringify(results)); res.status(200).send(results); }) - .catch((error) => res.status(404).send(error)); - + .catch(error => res.status(404).send(error)); }; const getProjectMembers = async function (req, res) { @@ -94,14 +100,14 @@ const userProfileController = function (UserProfile) { $in: [req.params.projectId], }, }, - "_id firstName email", + '_id firstName email', (err, profiles) => { if (err) { - res.status(404).send("Error finding user profiles"); + res.status(404).send('Error finding user profiles'); return; } res.json(profiles); - } + }, ); }; @@ -119,15 +125,15 @@ const userProfileController = function (UserProfile) { const userByEmail = await UserProfile.findOne({ email: { $regex: escapeRegex(req.body.email), - $options: "i", + $options: 'i', }, }); if (userByEmail) { res.status(400).send({ error: - "That email address is already in use. Please choose another email address.", - type: "email", + 'That email address is already in use. Please choose another email address.', + type: 'email', }); return; } @@ -146,8 +152,8 @@ const userProfileController = function (UserProfile) { if (userByPhoneNumber) { res.status(400).send({ error: - "That phone number is already in use. Please choose another number.", - type: "phoneNumber", + 'That phone number is already in use. Please choose another number.', + type: 'phoneNumber', }); return; } @@ -161,8 +167,8 @@ const userProfileController = function (UserProfile) { if (userDuplicateName && !req.body.allowsDuplicateName) { res.status(400).send({ error: - "That name is already in use. Please confirm if you want to use this name.", - type: "name", + 'That name is already in use. Please confirm if you want to use this name.', + type: 'name', }); return; } @@ -189,15 +195,15 @@ const userProfileController = function (UserProfile) { up.projects = Array.from(new Set(req.body.projects)); up.createdDate = req.body.createdDate; up.email = req.body.email; - up.weeklySummaries = req.body.weeklySummaries || [{ summary: "" }]; + up.weeklySummaries = req.body.weeklySummaries || [{ summary: '' }]; up.weeklySummariesCount = req.body.weeklySummariesCount || 0; up.weeklySummaryOption = req.body.weeklySummaryOption; - up.mediaUrl = req.body.mediaUrl || ""; - up.collaborationPreference = req.body.collaborationPreference || ""; - up.timeZone = req.body.timeZone || "America/Los_Angeles"; + up.mediaUrl = req.body.mediaUrl || ''; + up.collaborationPreference = req.body.collaborationPreference || ''; + up.timeZone = req.body.timeZone || 'America/Los_Angeles'; up.location = req.body.location; up.permissions = req.body.permissions; - up.bioPosted = req.body.bioPosted || "default"; + up.bioPosted = req.body.bioPosted || 'default'; up.isFirstTimelog = true; up.save() @@ -219,11 +225,11 @@ const userProfileController = function (UserProfile) { lastName: up.lastName, email: up.email, }; - const allUserCache = JSON.parse(cache.getCache("allusers")); + const allUserCache = JSON.parse(cache.getCache('allusers')); allUserCache.push(userCache); - cache.setCache("allusers", JSON.stringify(allUserCache)); + cache.setCache('allusers', JSON.stringify(allUserCache)); }) - .catch((error) => res.status(501).send(error)); + .catch(error => res.status(501).send(error)); }; const putUserProfile = async function (req, res) { @@ -234,12 +240,12 @@ const userProfileController = function (UserProfile) { || req.body.requestor.requestorId === userid ) ); - - const canEditTeamCode = req.body.requestor.role === "Owner" || - req.body.requestor.permissions?.frontPermissions.includes("editTeamCode"); + + const canEditTeamCode = req.body.requestor.role === 'Owner' + || req.body.requestor.permissions?.frontPermissions.includes('editTeamCode'); if (!isRequestorAuthorized) { - res.status(403).send("You are not authorized to update this user"); + res.status(403).send('You are not authorized to update this user'); return; } @@ -251,7 +257,7 @@ const userProfileController = function (UserProfile) { cache.removeCache(`user-${userid}`); UserProfile.findById(userid, async (err, record) => { if (err || !record) { - res.status(404).send("No valid records found"); + res.status(404).send('No valid records found'); return; } // validate userprofile pic @@ -270,8 +276,7 @@ const userProfileController = function (UserProfile) { : []; record.jobTitle = req.body.jobTitle; record.emailPubliclyAccessible = req.body.emailPubliclyAccessible; - record.phoneNumberPubliclyAccessible = - req.body.phoneNumberPubliclyAccessible; + record.phoneNumberPubliclyAccessible = req.body.phoneNumberPubliclyAccessible; record.profilePic = req.body.profilePic; record.firstName = req.body.firstName; @@ -293,25 +298,25 @@ const userProfileController = function (UserProfile) { record.isVisible = req.body.isVisible || false; record.isRehireable = req.body.isRehireable || false; record.totalIntangibleHrs = req.body.totalIntangibleHrs; - record.bioPosted = req.body.bioPosted || "default"; + record.bioPosted = req.body.bioPosted || 'default'; record.isFirstTimelog = req.body.isFirstTimelog; record.teamCode = req.body.teamCode; - if(!canEditTeamCode && record.teamCode !== req.body.teamCode){ - res.status(403).send("You are not authorized to edit team code."); + if (!canEditTeamCode && record.teamCode !== req.body.teamCode) { + res.status(403).send('You are not authorized to edit team code.'); return; } record.teamCode = req.body.teamCode; // find userData in cache - const isUserInCache = cache.hasCache("allusers"); + const isUserInCache = cache.hasCache('allusers'); let allUserData; let userData; let userIdx; if (isUserInCache) { - allUserData = JSON.parse(cache.getCache("allusers")); - userIdx = allUserData.findIndex((users) => users._id === userid); + allUserData = JSON.parse(cache.getCache('allusers')); + userIdx = allUserData.findIndex(users => users._id === userid); userData = allUserData[userIdx]; } if (await hasPermission(req.body.requestor, 'putUserProfileImportantInfo')) { @@ -326,11 +331,11 @@ const userProfileController = function (UserProfile) { // If their last update was made today, remove that const lasti = record.weeklycommittedHoursHistory.length - 1; const lastChangeDate = moment( - record.weeklycommittedHoursHistory[lasti].dateChanged + record.weeklycommittedHoursHistory[lasti].dateChanged, ); const now = moment(); - if (lastChangeDate.isSame(now, "day")) { + if (lastChangeDate.isSame(now, 'day')) { record.weeklycommittedHoursHistory.pop(); } @@ -343,8 +348,7 @@ const userProfileController = function (UserProfile) { record.weeklycommittedHoursHistory.push(newEntry); } - record.missedHours = - req.body.role === "Core Team" ? req.body?.missedHours ?? 0 : 0; + record.missedHours = req.body.role === 'Core Team' ? req.body?.missedHours ?? 0 : 0; record.adminLinks = req.body.adminLinks; record.teams = Array.from(new Set(req.body.teams)); record.projects = Array.from(new Set(req.body.projects)); @@ -376,8 +380,7 @@ const userProfileController = function (UserProfile) { record.weeklycommittedHoursHistory.push(newEntry); } // then also change the first committed history (index 0) - record.weeklycommittedHoursHistory[0].dateChanged = - record.createdDate; + record.weeklycommittedHoursHistory[0].dateChanged = record.createdDate; } record.bioPosted = req.body.bioPosted || 'default'; @@ -392,7 +395,7 @@ const userProfileController = function (UserProfile) { userData.endDate = record.endDate.toISOString(); } } else { - record.set("endDate", undefined, { strict: false }); + record.set('endDate', undefined, { strict: false }); } if (isUserInCache) { userData.role = record.role; @@ -414,7 +417,7 @@ const userProfileController = function (UserProfile) { results.infringements, results.firstName, results.lastName, - results.email + results.email, ); res.status(200).json({ _id: record._id, @@ -423,10 +426,10 @@ const userProfileController = function (UserProfile) { // update alluser cache if we have cache if (isUserInCache) { allUserData.splice(userIdx, 1, userData); - cache.setCache("allusers", JSON.stringify(allUserData)); + cache.setCache('allusers', JSON.stringify(allUserData)); } }) - .catch((error) => res.status(400).send(error)); + .catch(error => res.status(400).send(error)); }); }; @@ -571,15 +574,14 @@ const userProfileController = function (UserProfile) { const { userId } = req.params; const { key, value } = req.body; - if (key === "teamCode") { - const canEditTeamCode = req.body.requestor.role === "Owner" || - req.body.requestor.permissions?.frontPermissions.includes("editTeamCode"); + if (key === 'teamCode') { + const canEditTeamCode = req.body.requestor.role === 'Owner' + || req.body.requestor.permissions?.frontPermissions.includes('editTeamCode'); - if(!canEditTeamCode){ - res.status(403).send("You are not authorized to edit team code."); + if (!canEditTeamCode) { + res.status(403).send('You are not authorized to edit team code.'); return; } - } // remove user from cache, it should be loaded next time @@ -618,25 +620,26 @@ const userProfileController = function (UserProfile) { error: 'One of more required fields are missing', }); } - // Verify request is authorized by self or adminsitrator - if (!userId === requestor.requestorId && !await hasPermission(req.body.requestor, 'updatePassword')) { - return res.status(403).send({ - error: "You are unauthorized to update this user's password", - }); - } + // Check if the requestor has the permission to update passwords. + const hasUpdatePasswordPermission = await hasPermission(requestor.role, 'updatePassword'); - if (canRequestorUpdateUser(requestor.requestorId, userId)) { - return res.status(403).send({ - error: "You are unauthorized to update this user's password", - }); + // If the requestor is updating their own password, allow them to proceed. + if (userId === requestor.requestorId) { + console.log('Requestor is updating their own password'); + } + // Else if they're updating someone else's password, they need the 'updatePassword' permission. + else if (!hasUpdatePasswordPermission) { + console.log("Requestor is trying to update someone else's password but lacks the 'updatePassword' permission"); + return res.status(403).send({ + error: "You are unauthorized to update this user's password", + }); } // Verify new and confirm new password are correct - if (req.body.newpassword !== req.body.confirmnewpassword) { - res.status(400).send({ - error: 'New and confirm new passwords are not same', - }); + return res.status(400).send({ + error: 'New and confirm new passwords are not the same', + }); } // Verify old and new passwords are not same diff --git a/src/helpers/dashboardhelper.js b/src/helpers/dashboardhelper.js index d4ecb0d8d..67c8b0108 100644 --- a/src/helpers/dashboardhelper.js +++ b/src/helpers/dashboardhelper.js @@ -69,6 +69,13 @@ const dashboardhelper = function () { { $lte: ['$$timeentry.dateOfWork', pdtend], }, + { + $not: [ + { + $in: ['$$timeentry.entryType', ['person', 'team', 'project']], + }, + ], + }, ], }, }, @@ -200,7 +207,7 @@ const dashboardhelper = function () { role: 'Administrator', }, { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, - ] + ], }, { $and: [ @@ -291,6 +298,13 @@ const dashboardhelper = function () { { $lte: ['$$timeentry.dateOfWork', pdtend], }, + { + $not: [ + { + $in: ['$$timeentry.entryType', ['person', 'team', 'project']], + }, + ], + }, ], }, }, @@ -424,7 +438,6 @@ const dashboardhelper = function () { }, }, ]); - return output; }; @@ -454,6 +467,7 @@ const dashboardhelper = function () { $gte: pdtStart, $lte: pdtEnd, }, + entryType: { $in: ['default', null] }, personId: userId, }); @@ -593,6 +607,13 @@ const dashboardhelper = function () { { $lte: ['$$timeentry.dateOfWork', todate], }, + { + $not: [ + { + $in: ['$$timeentry.entryType', ['person', 'team', 'project']], + }, + ], + }, ], }, }, @@ -670,6 +691,13 @@ const dashboardhelper = function () { { $lte: ['$$timeentry.dateOfWork', todate], }, + { + $not: [ + { + $in: ['$$timeentry.entryType', ['person', 'team', 'project']], + }, + ], + }, ], }, }, diff --git a/src/helpers/reporthelper.js b/src/helpers/reporthelper.js index df09bc294..4de3f0517 100644 --- a/src/helpers/reporthelper.js +++ b/src/helpers/reporthelper.js @@ -1,5 +1,5 @@ -const moment = require("moment-timezone"); -const userProfile = require("../models/userProfile"); +const moment = require('moment-timezone'); +const userProfile = require('../models/userProfile'); /** * @@ -8,9 +8,9 @@ const userProfile = require("../models/userProfile"); * @returns The absolute value of the difference in weeks between the two input dates. */ const absoluteDifferenceInWeeks = (dateOfWork, pstEnd) => { - dateOfWork = moment(dateOfWork).endOf("week"); - pstEnd = moment(pstEnd).tz("America/Los_Angeles").endOf("week"); - return Math.abs(dateOfWork.diff(pstEnd, "weeks")); + dateOfWork = moment(dateOfWork).endOf('week'); + pstEnd = moment(pstEnd).tz('America/Los_Angeles').endOf('week'); + return Math.abs(dateOfWork.diff(pstEnd, 'weeks')); }; const reporthelper = function () { @@ -23,14 +23,14 @@ const reporthelper = function () { */ const weeklySummaries = async (startWeekIndex, endWeekIndex) => { const pstStart = moment() - .tz("America/Los_Angeles") - .startOf("week") - .subtract(startWeekIndex, "week") + .tz('America/Los_Angeles') + .startOf('week') + .subtract(startWeekIndex, 'week') .toDate(); const pstEnd = moment() - .tz("America/Los_Angeles") - .endOf("week") - .subtract(endWeekIndex, "week") + .tz('America/Los_Angeles') + .endOf('week') + .subtract(endWeekIndex, 'week') .toDate(); const results = await userProfile.aggregate([ @@ -39,33 +39,33 @@ const reporthelper = function () { }, { $lookup: { - from: "timeEntries", - localField: "_id", - foreignField: "personId", - as: "timeEntries", + from: 'timeEntries', + localField: '_id', + foreignField: 'personId', + as: 'timeEntries', }, }, { - $set: { totalTangibleHrs: { $objectToArray: "$hoursByCategory" } }, + $set: { totalTangibleHrs: { $objectToArray: '$hoursByCategory' } }, }, { $project: { timeEntries: { $filter: { - input: "$timeEntries", - as: "timeEntry", + input: '$timeEntries', + as: 'timeEntry', cond: { $and: [ { $gte: [ - "$$timeEntry.dateOfWork", - moment(pstStart).format("YYYY-MM-DD"), + '$$timeEntry.dateOfWork', + moment(pstStart).format('YYYY-MM-DD'), ], }, { $lte: [ - "$$timeEntry.dateOfWork", - moment(pstEnd).format("YYYY-MM-DD"), + '$$timeEntry.dateOfWork', + moment(pstEnd).format('YYYY-MM-DD'), ], }, ], @@ -87,22 +87,22 @@ const reporthelper = function () { toggleTrophyIcon: 1, badgeCollection: { $filter: { - input: "$badgeCollection", - as: "badge", + input: '$badgeCollection', + as: 'badge', cond: { $or: [ { $and: [ { $gte: [ - "$$badge.earnedDate", - moment(pstStart).format("YYYY-MM-DD"), + '$$badge.earnedDate', + moment(pstStart).format('YYYY-MM-DD'), ], }, { $lte: [ - "$$badge.earnedDate", - moment(pstEnd).format("YYYY-MM-DD"), + '$$badge.earnedDate', + moment(pstEnd).format('YYYY-MM-DD'), ], }, ], @@ -110,10 +110,10 @@ const reporthelper = function () { { $and: [ { - $gte: ["$$badge.lastModified", pstStart], + $gte: ['$$badge.lastModified', pstStart], }, { - $lte: ["$$badge.lastModified", pstEnd], + $lte: ['$$badge.lastModified', pstEnd], }, ], }, @@ -127,15 +127,15 @@ const reporthelper = function () { role: 1, weeklySummaries: { $filter: { - input: "$weeklySummaries", - as: "ws", + input: '$weeklySummaries', + as: 'ws', cond: { $and: [ { - $gte: ["$$ws.dueDate", pstStart], + $gte: ['$$ws.dueDate', pstStart], }, { - $lte: ["$$ws.dueDate", pstEnd], + $lte: ['$$ws.dueDate', pstEnd], }, ], }, @@ -143,13 +143,13 @@ const reporthelper = function () { }, weeklySummariesCount: 1, isTangible: 1, - totalTangibleHrs: { $sum: "$totalTangibleHrs.v" }, + totalTangibleHrs: { $sum: '$totalTangibleHrs.v' }, daysInTeam: { $dateDiff: { - startDate: "$createdDate", + startDate: '$createdDate', endDate: new Date(), - unit: "day", - timezone: "America/Los_Angeles", + unit: 'day', + timezone: 'America/Los_Angeles', }, }, }, @@ -163,8 +163,8 @@ const reporthelper = function () { result.timeEntries.forEach((entry) => { const index = absoluteDifferenceInWeeks(entry.dateOfWork, pstEnd); if ( - result.totalSeconds[index] === undefined || - result.totalSeconds[index] === null + result.totalSeconds[index] === undefined + || result.totalSeconds[index] === null ) { result.totalSeconds[index] = 0; } @@ -190,16 +190,16 @@ const reporthelper = function () { */ const doesDateBelongToWeek = function (dueDate, weekIndex) { const pstStartOfWeek = moment() - .tz("America/Los_Angeles") - .startOf("week") - .subtract(weekIndex, "week"); + .tz('America/Los_Angeles') + .startOf('week') + .subtract(weekIndex, 'week'); const pstEndOfWeek = moment() - .tz("America/Los_Angeles") - .endOf("week") - .subtract(weekIndex, "week"); + .tz('America/Los_Angeles') + .endOf('week') + .subtract(weekIndex, 'week'); const fromDate = moment(pstStartOfWeek).toDate(); const toDate = moment(pstEndOfWeek).toDate(); - return moment(dueDate).isBetween(fromDate, toDate, undefined, "[]"); + return moment(dueDate).isBetween(fromDate, toDate, undefined, '[]'); }; /** diff --git a/src/helpers/taskHelper.js b/src/helpers/taskHelper.js index a94aaee94..cfb1235fd 100644 --- a/src/helpers/taskHelper.js +++ b/src/helpers/taskHelper.js @@ -43,7 +43,7 @@ const taskHelper = function () { // dashboard tasks user roles hierarchy $or: [ { - role: { $in: ['Owner', 'Core Team'] }, + role: { $in: ['Owner', 'Core Team'] }, }, { $and: [ @@ -51,7 +51,7 @@ const taskHelper = function () { role: 'Administrator', }, { 'persondata.0.role': { $nin: ['Owner', 'Administrator'] } }, - ] + ], }, { $and: [ @@ -113,6 +113,9 @@ const taskHelper = function () { { $lte: ['$$timeentry.dateOfWork', pdtend], }, + { + $in: ['$$timeentry.entryType', ['default', null]], + }, ], }, }, @@ -357,6 +360,9 @@ const taskHelper = function () { { $lte: ['$$timeentry.dateOfWork', pdtend], }, + { + $in: ['$$timeentry.entryType', ['default', null]], + }, ], }, }, diff --git a/src/helpers/userHelper.js b/src/helpers/userHelper.js index 8bf04eb5f..1033bab2f 100644 --- a/src/helpers/userHelper.js +++ b/src/helpers/userHelper.js @@ -15,6 +15,7 @@ const hasPermission = require("../utilities/permissions"); const Reason = require("../models/reason"); const token = require("../models/profileInitialSetupToken"); + const userHelper = function () { const getTeamMembers = function (user) { const userId = mongoose.Types.ObjectId(user._id); @@ -27,21 +28,12 @@ const userHelper = function () { }); }; + // Updated By: Shengwei + // Updated Date: 12/08/2023 + // Update format to "MMM-DD-YY" from "YYYY-MMM-DD" (Confirmed with Jae) const earnedDateBadge = () => { - const today = new Date(); - const yyyy = today.getFullYear(); - // Add 1 beacuse the month start at zero - let mm = today.getMonth() + 1; - let dd = today.getDate(); - - // eslint-disable-next-line no-unused-expressions - mm = mm < 10 ? `0${mm}` : mm; - // eslint-disable-next-line no-unused-expressions - dd = dd < 10 ? `0${dd}` : dd; - - const formatedDate = `${yyyy}-${mm}-${dd}`; - - return formatedDate; + const currentDate = new Date(Date.now()); + return moment(currentDate).tz('America/Los_Angeles').format('MMM-DD-YY'); }; const getUserName = async function (userId) { @@ -492,8 +484,8 @@ const userHelper = function () { emailBody, null, "onecommunityglobal@gmail.com", - null, - status.email + status.email, + null ); const categories = await dashboardHelper.laborThisWeekByCategory( @@ -592,9 +584,9 @@ const userHelper = function () { }, { $lookup: { - from: "timeEntries", - localField: "_id", - foreignField: "personId", + from: 'timeEntries', + localField: '_id', + foreignField: 'personId', pipeline: [ { $match: { @@ -603,6 +595,7 @@ const userHelper = function () { { $eq: ["$isTangible", true] }, { $gte: ["$dateOfWork", startOfLastWeek] }, { $lte: ["$dateOfWork", endOfLastWeek] }, + { $in: ["$entryType", 'default', null] }, ], }, }, @@ -1622,4 +1615,4 @@ const userHelper = function () { }; }; -module.exports = userHelper; +module.exports = userHelper; \ No newline at end of file diff --git a/src/models/badge.js b/src/models/badge.js index 8b2c754e7..e3e93eaaf 100644 --- a/src/models/badge.js +++ b/src/models/badge.js @@ -19,7 +19,7 @@ const Badge = new Schema({ imageUrl: { type: String }, ranking: { type: Number }, description: { type: String }, - showReport: {type: Boolean}, + showReport: { type: Boolean }, }); module.exports = mongoose.model('badge', Badge, 'badges'); diff --git a/src/models/bmdashboard/buildingInventoryItem.js b/src/models/bmdashboard/buildingInventoryItem.js new file mode 100644 index 000000000..5e8b24916 --- /dev/null +++ b/src/models/bmdashboard/buildingInventoryItem.js @@ -0,0 +1,148 @@ +const mongoose = require('mongoose'); + +//----------------------- +// BASE INVENTORY SCHEMAS +//----------------------- + +// TODO: purchaseRecord subdocs may be changed to purchaseRequests. A new purchaseRecord subdoc may be added to track purchases and costs for the item. + +// SMALL ITEMS BASE +// base schema for Consumable, Material, Reusable +// documents stored in 'buildingInventoryItems' collection + +const smallItemBaseSchema = mongoose.Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + stockBought: { type: Number, default: 0 }, // total amount of item bought for use in the project + // TODO: can stockAvailable default be a function? + stockAvailable: { type: Number, default: 0 }, // available = bought - (used + wasted/destroyed) + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantity: { type: Number, required: true, default: 1 }, // default 1 for tool or equipment purchases + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brandPref: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], + updateRecord: [{ + _id: false, + date: { type: Date, required: true }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + quantityUsed: { type: Number, required: true }, + quantityWasted: { type: Number, required: true }, + }], +}); + +const smallItemBase = mongoose.model('smallItemBase', smallItemBaseSchema, 'buildingInventoryItems'); + +// LARGE ITEMS BASE +// base schema for Tool, Equipment +// documents stored in 'buildingInventoryItems' collection + +const largeItemBaseSchema = mongoose.Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + project: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingProject' }, + purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // rental fields are required if purchaseStatus = "Rental" (hopefully correct syntax) + rentedOnDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + rentalDueDate: { type: Date, required: () => this.purchaseStatus === 'Rental' }, + imageUrl: String, + purchaseRecord: [{ + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + makeModelPref: String, + estTimeRequired: { type: Number, required: true }, // estimated time required on site + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], + updateRecord: [{ // track tool condition updates + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, + }], + logRecord: [{ // track tool daily check in/out and responsible user + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + type: { type: String, enum: ['Check In', 'Check Out'] }, + }], +}); + +const largeItemBase = mongoose.model('largeItemBase', largeItemBaseSchema, 'buildingInventoryItems'); + +//----------------- +// MATERIALS SCHEMA +//----------------- + +// inherits all properties of smallItemBaseSchema +// each document derived from this schema includes key field { __t: "material" } +// ex: sand, stone, bricks, lumber, insulation + +const buildingMaterial = smallItemBase.discriminator('material', new mongoose.Schema({ + stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused + stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock +})); + +//------------------ +// CONSUMABLE SCHEMA +//------------------ + +// inherits all properties of smallItemBaseSchema +// each document derived from this schema includes key field { __t: "consumable" } +// ex: screws, nails, staples + +const buildingConsumable = smallItemBase.discriminator('consumable', new mongoose.Schema({ + stockUsed: { type: Number, default: 0 }, // stock that has been used up and cannot be reused + stockWasted: { type: Number, default: 0 }, // ruined or destroyed stock +})); + +//---------------- +// REUSABLE SCHEMA +//---------------- + +// inherits all properties of smallItemBaseSchema +// each document derived from this schema includes key field { __t: "reusable" } +// ex: hammers, screwdrivers, mallets, brushes, gloves + +const buildingReusable = smallItemBase.discriminator('reusable', new mongoose.Schema({ + stockDestroyed: { type: Number, default: 0 }, +})); + +//------------ +// TOOL SCHEMA +//------------ + +// inherits all properties of largeItemBaseSchema +// each document derived from this schema includes key field { __t: "tool" } +// ex: power drills, wheelbarrows, shovels, jackhammers + +const buildingTool = largeItemBase.discriminator('tool', new mongoose.Schema({ + code: { type: Number, required: true }, // TODO: add function to create simple numeric code for on-site tool tracking +})); + + +//----------------- +// EQUIPMENT SCHEMA +//----------------- + +// inherits all properties of largeItemBaseSchema +// each document derived from this schema includes key field { __t: "equipment" } +// items in this category are assumed to be rented +// ex: tractors, excavators, bulldozers + +const buildingEquipment = largeItemBase.discriminator('equipment', new mongoose.Schema({ + isTracked: { type: Boolean, required: true }, // has asset tracker + assetTracker: { type: String, required: () => this.isTracked }, // required if isTracked = true (syntax?) +})); + +module.exports = { + buildingMaterial, + buildingConsumable, + buildingReusable, + buildingTool, + buildingEquipment, +}; diff --git a/src/models/bmdashboard/buildingMaterial.js b/src/models/bmdashboard/buildingMaterial.js index 4170443e0..bc86884ed 100644 --- a/src/models/bmdashboard/buildingMaterial.js +++ b/src/models/bmdashboard/buildingMaterial.js @@ -26,5 +26,4 @@ const buildingMaterial = new Schema({ quantityWasted: { type: Number, required: true }, }], }); - module.exports = mongoose.model('buildingMaterial', buildingMaterial, 'buildingMaterials'); diff --git a/src/models/bmdashboard/buildingProject.js b/src/models/bmdashboard/buildingProject.js index 566bc124e..3ca4bf993 100644 --- a/src/models/bmdashboard/buildingProject.js +++ b/src/models/bmdashboard/buildingProject.js @@ -8,8 +8,12 @@ const buildingProject = new Schema({ template: String, // construction template (ie Earthbag Village) location: String, // use lat/lng instead? dateCreated: { type: Date, default: Date.now }, - buildingManager: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, // BM's id - team: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }], + buildingManager: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + teams: [{ type: mongoose.SchemaTypes.ObjectId, ref: 'teams' }], // teams assigned to the project + members: [{ + user: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + hours: { type: Number, default: 0 }, // tracked via the Member Check-In Page timer + }], }); module.exports = mongoose.model('buildingProject', buildingProject, 'buildingProjects'); diff --git a/src/models/bmdashboard/buildingTool.js b/src/models/bmdashboard/buildingTool.js new file mode 100644 index 000000000..95be0c4d5 --- /dev/null +++ b/src/models/bmdashboard/buildingTool.js @@ -0,0 +1,36 @@ +const mongoose = require('mongoose'); + +const { Schema } = mongoose; + +const buildingTool = new Schema({ + itemType: { type: mongoose.SchemaTypes.ObjectId, ref: 'buildingInventoryType' }, + code: { type: Number, required: true }, // add function to create code for on-site tool tracking + purchaseStatus: { type: String, enum: ['Rental', 'Purchase'], required: true }, + // add discriminator based on rental or purchase so these fields are required if tool is rented + rentedOnDate: Date, + rentalDue: Date, + userResponsible: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + purchaseRecord: [{ // track purchase/rental requests + _id: false, // do not add _id field to subdocument + date: { type: Date, default: Date.now() }, + requestedBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + priority: { type: String, enum: ['Low', 'Medium', 'High'], required: true }, + brand: String, + status: { type: String, default: 'Pending', enum: ['Approved', 'Pending', 'Rejected'] }, + }], + updateRecord: [{ // track tool condition updates + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + condition: { type: String, enum: ['Good', 'Needs Repair', 'Out of Order'] }, + }], + logRecord: [{ // track tool daily check in/out and use + _id: false, + date: { type: Date, default: Date.now() }, + createdBy: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + responsibleUser: { type: mongoose.SchemaTypes.ObjectId, ref: 'userProfile' }, + type: { type: String, enum: ['Check In', 'Check Out'] }, // default = opposite of current log status? + }], +}); + +module.exports = mongoose.model('buildingTool', buildingTool, 'buildingTools'); diff --git a/src/models/ownerMessage.js b/src/models/ownerMessage.js index be953c3a3..a6314c929 100644 --- a/src/models/ownerMessage.js +++ b/src/models/ownerMessage.js @@ -3,7 +3,8 @@ const mongoose = require('mongoose'); const { Schema } = mongoose; const OwnerMessage = new Schema({ - message: { type: String }, + message: { type: String, default: '' }, + standardMessage: { type: String, default: '' }, }); module.exports = mongoose.model('ownerMessage', OwnerMessage, 'ownerMessage'); diff --git a/src/models/ownerStandardMessage.js b/src/models/ownerStandardMessage.js deleted file mode 100644 index d344a773f..000000000 --- a/src/models/ownerStandardMessage.js +++ /dev/null @@ -1,9 +0,0 @@ -const mongoose = require('mongoose'); - -const { Schema } = mongoose; - -const OwnerStandardMessage = new Schema({ - message: { type: String }, -}); - -module.exports = mongoose.model('ownerStandardMessage', OwnerStandardMessage, 'ownerStandardMessage'); diff --git a/src/models/profileInitialSetupToken.js b/src/models/profileInitialSetupToken.js index 48413fb77..77b830229 100644 --- a/src/models/profileInitialSetupToken.js +++ b/src/models/profileInitialSetupToken.js @@ -10,7 +10,7 @@ const profileInitialSetupTokenSchema = new mongoose.Schema({ type: String, required: true, }, - weeklyCommittedHours : { + weeklyCommittedHours: { type: Number, required: true, default: 10, @@ -21,4 +21,4 @@ const profileInitialSetupTokenSchema = new mongoose.Schema({ }, }); -module.exports = mongoose.model('profileInitialSetupToken', profileInitialSetupTokenSchema, 'profileInitialSetupToken'); \ No newline at end of file +module.exports = mongoose.model('profileInitialSetupToken', profileInitialSetupTokenSchema, 'profileInitialSetupToken'); diff --git a/src/models/timeentry.js b/src/models/timeentry.js index aeef5fdc7..83510773f 100644 --- a/src/models/timeentry.js +++ b/src/models/timeentry.js @@ -4,8 +4,10 @@ const { Schema } = mongoose; const TimeEntry = new Schema({ - personId: { type: Schema.Types.ObjectId, required: [true, 'Resource is a required field'], ref: 'userProfile' }, - projectId: { type: Schema.Types.ObjectId, required: [true, 'Project is a required field'], ref: 'project' }, + 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' }, dateOfWork: { type: String, required: true }, totalSeconds: { type: Number }, notes: { type: String }, diff --git a/src/routes/bmdashboard/bmMaterialsRouter.js b/src/routes/bmdashboard/bmMaterialsRouter.js index da29c35d7..51c5f437f 100644 --- a/src/routes/bmdashboard/bmMaterialsRouter.js +++ b/src/routes/bmdashboard/bmMaterialsRouter.js @@ -6,6 +6,14 @@ const routes = function (itemMaterial, buildingMaterial) { materialsRouter.route('/materials') .get(controller.bmMaterialsList) .post(controller.bmPurchaseMaterials); + + materialsRouter.route('/updateMaterialRecord') + .post(controller.bmPostMaterialUpdateRecord); + + materialsRouter.route('/updateMaterialRecordBulk') + .post(controller.bmPostMaterialUpdateBulk); + + return materialsRouter; }; diff --git a/src/routes/isEmailExistsRouter.js b/src/routes/isEmailExistsRouter.js index cfb4e6033..d19b14fe2 100644 --- a/src/routes/isEmailExistsRouter.js +++ b/src/routes/isEmailExistsRouter.js @@ -2,14 +2,14 @@ const express = require('express'); const routes = function () { - const controller = require('../controllers/isEmailExistsController')() + const controller = require('../controllers/isEmailExistsController')(); - const isEmailExistsRouter = express.Router() + const isEmailExistsRouter = express.Router(); - isEmailExistsRouter.route("/is-email-exists/:email") - .get(controller.isEmailExists) + isEmailExistsRouter.route('/is-email-exists/:email') + .get(controller.isEmailExists); - return isEmailExistsRouter -} + return isEmailExistsRouter; +}; -module.exports = routes +module.exports = routes; diff --git a/src/routes/ownerMessageRouter.js b/src/routes/ownerMessageRouter.js index e436deed8..6f5716fe9 100644 --- a/src/routes/ownerMessageRouter.js +++ b/src/routes/ownerMessageRouter.js @@ -5,14 +5,11 @@ const routes = function (ownerMessage) { const OwnerMessageRouter = express.Router(); OwnerMessageRouter.route('/ownerMessage') - .post(controller.postOwnerMessage) - .get(controller.getOwnerMessage) - .delete(controller.deleteOwnerMessage); + .get(controller.getOwnerMessage) + .put(controller.updateOwnerMessage) + .delete(controller.deleteOwnerMessage); - OwnerMessageRouter.route('/ownerMessage/:id') - .put(controller.updateOwnerMessage); - -return OwnerMessageRouter; + return OwnerMessageRouter; }; module.exports = routes; diff --git a/src/routes/ownerStandardMessageRouter.js b/src/routes/ownerStandardMessageRouter.js deleted file mode 100644 index 08e629c3d..000000000 --- a/src/routes/ownerStandardMessageRouter.js +++ /dev/null @@ -1,18 +0,0 @@ -const express = require('express'); - -const routes = function (ownerStandardMessage) { - const controller = require('../controllers/ownerStandardMessageController')(ownerStandardMessage); - const OwnerStandardMessageRouter = express.Router(); - - OwnerStandardMessageRouter.route('/ownerStandardMessage') - .post(controller.postOwnerStandardMessage) - .get(controller.getOwnerStandardMessage) - .delete(controller.deleteOwnerStandardMessage); - - OwnerStandardMessageRouter.route('/ownerStandardMessage/:id') - .put(controller.updateOwnerStandardMessage); - -return OwnerStandardMessageRouter; -}; - -module.exports = routes; diff --git a/src/routes/profileInitialSetupRouter.js b/src/routes/profileInitialSetupRouter.js index a23c6a868..2091afcad 100644 --- a/src/routes/profileInitialSetupRouter.js +++ b/src/routes/profileInitialSetupRouter.js @@ -5,9 +5,9 @@ const routes = function (ProfileInitialSetupToken, userProfile, Project) { const controller = require('../controllers/profileInitialSetupController')(ProfileInitialSetupToken, userProfile, Project); ProfileInitialSetup.route('/getInitialSetuptoken') .post(controller.getSetupToken); - ProfileInitialSetup.route('/ProfileInitialSetup').post(controller.setUpNewUser) - ProfileInitialSetup.route('/validateToken').post(controller.validateSetupToken) - ProfileInitialSetup.route('/getTimeZoneAPIKeyByToken').post(controller.getTimeZoneAPIKeyByToken) + ProfileInitialSetup.route('/ProfileInitialSetup').post(controller.setUpNewUser); + ProfileInitialSetup.route('/validateToken').post(controller.validateSetupToken); + ProfileInitialSetup.route('/getTimeZoneAPIKeyByToken').post(controller.getTimeZoneAPIKeyByToken); return ProfileInitialSetup; }; diff --git a/src/routes/timeentryRouter.js b/src/routes/timeentryRouter.js index b319aa595..0562f49ed 100644 --- a/src/routes/timeentryRouter.js +++ b/src/routes/timeentryRouter.js @@ -19,6 +19,15 @@ const routes = function (TimeEntry) { TimeEntryRouter.route('/TimeEntry/users') .post(controller.getTimeEntriesForUsersList); + TimeEntryRouter.route('/TimeEntry/lostUsers') + .post(controller.getLostTimeEntriesForUserList); + + TimeEntryRouter.route('/TimeEntry/lostProjects') + .post(controller.getLostTimeEntriesForProjectList); + + TimeEntryRouter.route('/TimeEntry/lostTeams') + .post(controller.getLostTimeEntriesForTeamList); + TimeEntryRouter.route('/TimeEntry/projects/:projectId/:fromDate/:toDate') .get(controller.getTimeEntriesForSpecifiedProject); diff --git a/src/startup/routes.js b/src/startup/routes.js index 2d95ea639..ff5d01b2b 100644 --- a/src/startup/routes.js +++ b/src/startup/routes.js @@ -16,7 +16,6 @@ const inventoryItemType = require('../models/inventoryItemType'); const role = require('../models/role'); const rolePreset = require('../models/rolePreset'); const ownerMessage = require('../models/ownerMessage'); -const ownerStandardMessage = require('../models/ownerStandardMessage'); const profileInitialSetuptoken = require('../models/profileInitialSetupToken'); const reason = require('../models/reason'); const mouseoverText = require('../models/mouseoverText'); @@ -55,7 +54,6 @@ const taskEditSuggestionRouter = require('../routes/taskEditSuggestionRouter')(t const roleRouter = require('../routes/roleRouter')(role); const rolePresetRouter = require('../routes/rolePresetRouter')(rolePreset); const ownerMessageRouter = require('../routes/ownerMessageRouter')(ownerMessage); -const ownerStandardMessageRouter = require('../routes/ownerStandardMessageRouter')(ownerStandardMessage); const reasonRouter = require('../routes/reasonRouter')(reason, userProfile); const mouseoverTextRouter = require('../routes/mouseoverTextRouter')(mouseoverText); @@ -92,7 +90,6 @@ module.exports = function (app) { app.use('/api', roleRouter); app.use('/api', rolePresetRouter); app.use('/api', ownerMessageRouter); - app.use('/api', ownerStandardMessageRouter); app.use('/api', profileInitialSetupRouter); app.use('/api', reasonRouter); app.use('/api', informationRouter); diff --git a/src/utilities/createInitialPermissions.js b/src/utilities/createInitialPermissions.js index 9cdf7f9e0..86a79b94c 100644 --- a/src/utilities/createInitialPermissions.js +++ b/src/utilities/createInitialPermissions.js @@ -6,6 +6,9 @@ const permissionsRoles = [ { roleName: 'Administrator', permissions: [ + // Reports + 'getWeeklySummaries', + 'getReports', // Doesn't do anything on back-end. // Badges 'seeBadges', 'assignBadges', @@ -65,8 +68,7 @@ const permissionsRoles = [ // General 'getUserProfiles', 'getProjectMembers', - 'getWeeklySummaries', - // 'getReportsPage',? + 'getTimeZoneAPIKey', 'checkLeadTeamOfXplus', ], @@ -97,6 +99,7 @@ const permissionsRoles = [ 'getAllInvType', 'postInvType', 'getWeeklySummaries', + 'getReports', 'getTimeZoneAPIKey', 'checkLeadTeamOfXplus', ], @@ -124,7 +127,6 @@ const permissionsRoles = [ 'putInvType', 'getAllInvType', 'postInvType', - 'getWeeklySummaries', 'getTimeZoneAPIKey', 'checkLeadTeamOfXplus', ], @@ -151,7 +153,6 @@ const permissionsRoles = [ 'putInvType', 'getAllInvType', 'postInvType', - 'getWeeklySummaries', 'getTimeZoneAPIKey', 'checkLeadTeamOfXplus', ], @@ -212,6 +213,7 @@ const permissionsRoles = [ 'getAllInvType', 'postInvType', 'getWeeklySummaries', + 'getReports', 'getTimeZoneAPIKey', 'checkLeadTeamOfXplus', 'editTeamCode', diff --git a/src/utilities/escapeRegex.js b/src/utilities/escapeRegex.js index 01a65ea50..cf7563e26 100644 --- a/src/utilities/escapeRegex.js +++ b/src/utilities/escapeRegex.js @@ -1,6 +1,6 @@ const escapeRegex = function (text) { - return `^${text.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&')}&`; + return `^${text.replace(/[[\]{}()*+?.\\^$|]/g, '\\$&')}$`; }; module.exports = escapeRegex; diff --git a/src/utilities/permissions.js b/src/utilities/permissions.js index 7b5d4a245..ebf35b2a1 100644 --- a/src/utilities/permissions.js +++ b/src/utilities/permissions.js @@ -4,12 +4,14 @@ const UserProfile = require('../models/userProfile'); const hasRolePermission = async (role, action) => Role.findOne({ roleName: role }) .exec() - .then(({ permissions }) => permissions.includes(action)); + .then(({ permissions }) => permissions.includes(action)) + .catch(false); const hasIndividualPermission = async (userId, action) => UserProfile.findById(userId) .select('permissions') .exec() - .then(({ permissions }) => permissions.frontPermissions.includes(action)); + .then(({ permissions }) => permissions.frontPermissions.includes(action)) + .catch(false); const hasPermission = async (requestor, action) => await hasRolePermission(requestor.role, action) || hasIndividualPermission(requestor.requestorId, action);