diff --git a/src/controllers/WeeklySummaryEmailAssignmentController.js b/src/controllers/WeeklySummaryEmailAssignmentController.js index 795e56a3e..2e7cb0e0a 100644 --- a/src/controllers/WeeklySummaryEmailAssignmentController.js +++ b/src/controllers/WeeklySummaryEmailAssignmentController.js @@ -1,108 +1,111 @@ -const WeeklySummaryEmailAssignmentController = function (WeeklySummaryEmailAssignment, userProfile) { - const getWeeklySummaryEmailAssignment = async function (req, res) { - try { - const assignments = await WeeklySummaryEmailAssignment.find().populate('assignedTo').exec(); - res.status(200).send(assignments); - } catch (error) { - res.status(500).send(error); +const WeeklySummaryEmailAssignmentController = function ( + WeeklySummaryEmailAssignment, + userProfile, +) { + const getWeeklySummaryEmailAssignment = async function (req, res) { + try { + const assignments = await WeeklySummaryEmailAssignment.find().populate('assignedTo').exec(); + res.status(200).send(assignments); + } catch (error) { + res.status(500).send(error); + } + }; + + const setWeeklySummaryEmailAssignment = async function (req, res) { + try { + const { email } = req.body; + + if (!email) { + res.status(400).send('bad request'); + return; } - }; - - const setWeeklySummaryEmailAssignment = async function (req, res) { - try { - const { email } = req.body; - - if (!email) { - res.status(400).send('bad request'); - return; - } - - const user = await userProfile.findOne({ email }); - if (!user) { - return res.status(400).send('User profile not found'); - } - - const newAssignment = new WeeklySummaryEmailAssignment({ - email, - assignedTo: user._id, - }); - - await newAssignment.save(); - const assignment = await WeeklySummaryEmailAssignment.find({ email }).populate('assignedTo').exec(); - - res.status(200).send(assignment[0]); - } catch (error) { - res.status(500).send(error); + + const user = await userProfile.findOne({ email }); + if (!user) { + return res.status(400).send('User profile not found'); } - }; - - const deleteWeeklySummaryEmailAssignment = async function (req, res) { - try { - const { id } = req.params; - - if (!id) { - res.status(400).send('bad request'); - return; - } - - const deletedAssignment = await WeeklySummaryEmailAssignment.findOneAndDelete({ _id: id }); - if (!deletedAssignment) { - res.status(404).send('Assignment not found'); - return; - } - - res.status(200).send({ id }); - } catch (error) { - res.status(500).send(error); + + const newAssignment = new WeeklySummaryEmailAssignment({ + email, + assignedTo: user._id, + }); + + await newAssignment.save(); + const assignment = await WeeklySummaryEmailAssignment.find({ email }) + .populate('assignedTo') + .exec(); + + res.status(200).send(assignment[0]); + } catch (error) { + res.status(500).send(error); + } + }; + + const deleteWeeklySummaryEmailAssignment = async function (req, res) { + try { + const { id } = req.params; + + if (!id) { + res.status(400).send('bad request'); + return; } - }; - - const updateWeeklySummaryEmailAssignment = async function (req, res) { - try{ - const { id } = req.params; - const { email } = req.body; - - if (!id || (!email)) { - res.status(400).send('bad request'); - return; - } - - const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; - if (!emailRegex.test(email)) { - return res.status(400).json({ - error: 'Invalid email format' - }); - } - - const updateAssignment = await WeeklySummaryEmailAssignment.findOneAndUpdate( - { _id: id }, - { - email, - }, - { - new: true - } - ); - - if (!updateAssignment) { - res.status(404).send('Assignment not found'); - return; - } - - res.status(200).send(updateAssignment); - - } catch (error) { - res.status(500).send(error); + + const deletedAssignment = await WeeklySummaryEmailAssignment.findOneAndDelete({ _id: id }); + if (!deletedAssignment) { + res.status(404).send('Assignment not found'); + return; } - }; - - return { - getWeeklySummaryEmailAssignment, - setWeeklySummaryEmailAssignment, - deleteWeeklySummaryEmailAssignment, - updateWeeklySummaryEmailAssignment - }; + + res.status(200).send({ id }); + } catch (error) { + res.status(500).send(error); + } }; - - module.exports = WeeklySummaryEmailAssignmentController; - \ No newline at end of file + + const updateWeeklySummaryEmailAssignment = async function (req, res) { + try { + const { id } = req.params; + const { email } = req.body; + + if (!id || !email) { + res.status(400).send('bad request'); + return; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(email)) { + return res.status(400).json({ + error: 'Invalid email format', + }); + } + + const updateAssignment = await WeeklySummaryEmailAssignment.findOneAndUpdate( + { _id: id }, + { + email, + }, + { + new: true, + }, + ); + + if (!updateAssignment) { + res.status(404).send('Assignment not found'); + return; + } + + res.status(200).send(updateAssignment); + } catch (error) { + res.status(500).send(error); + } + }; + + return { + getWeeklySummaryEmailAssignment, + setWeeklySummaryEmailAssignment, + deleteWeeklySummaryEmailAssignment, + updateWeeklySummaryEmailAssignment, + }; +}; + +module.exports = WeeklySummaryEmailAssignmentController; diff --git a/src/controllers/automation/sentryController.js b/src/controllers/automation/sentryController.js index 9f407114d..3b3fd09a0 100644 --- a/src/controllers/automation/sentryController.js +++ b/src/controllers/automation/sentryController.js @@ -3,7 +3,6 @@ const sentryService = require('../../services/automation/sentryService'); const appAccessService = require('../../services/automation/appAccessService'); const { checkAppAccess } = require('./utils'); - // Controller function to invite a user async function inviteUser(req, res) { const { targetUser } = req.body; @@ -15,11 +14,16 @@ async function inviteUser(req, res) { if (!checkAppAccess(requestor.role)) { res.status(403).send({ message: 'Unauthorized request' }); return; - } + } try { const invitation = await sentryService.inviteUser(targetUser.email); - await appAccessService.upsertAppAccess(targetUser.targetUserId, 'sentry', 'invited', targetUser.email); + await appAccessService.upsertAppAccess( + targetUser.targetUserId, + 'sentry', + 'invited', + targetUser.email, + ); res.status(201).json({ message: 'Invitation sent', data: invitation }); } catch (error) { res.status(500).json({ message: error.message }); @@ -50,8 +54,7 @@ async function removeUser(req, res) { } } - module.exports = { inviteUser, removeUser, -}; \ No newline at end of file +}; diff --git a/src/controllers/bmdashboard/bmEquipmentController.js b/src/controllers/bmdashboard/bmEquipmentController.js index 81feb1d23..729367748 100644 --- a/src/controllers/bmdashboard/bmEquipmentController.js +++ b/src/controllers/bmdashboard/bmEquipmentController.js @@ -148,7 +148,7 @@ const bmEquipmentController = (BuildingEquipment) => { return res.status(400).send({ error: 'Request body must be a non-empty array.' }); } - const invalid = updates.some(item => { + const invalid = updates.some((item) => { if (!item.equipmentId || !mongoose.Types.ObjectId.isValid(item.equipmentId)) { res.status(400).send({ error: 'Invalid or missing equipmentId.' }); return true; diff --git a/src/controllers/bmdashboard/projectCostTrackingController.js b/src/controllers/bmdashboard/projectCostTrackingController.js new file mode 100644 index 000000000..17dbf993a --- /dev/null +++ b/src/controllers/bmdashboard/projectCostTrackingController.js @@ -0,0 +1,191 @@ +const projectCostTrackingController = function (ProjectCostTracking) { + // Simple linear regression class compatible with older Node.js versions + class SimpleLinearRegression { + constructor(x, y) { + if (x.length !== y.length) { + throw new Error('X and Y arrays must have the same length'); + } + + const n = x.length; + const sumX = x.reduce((a, b) => a + b, 0); + const sumY = y.reduce((a, b) => a + b, 0); + const sumXY = x.reduce((acc, xi, i) => acc + xi * y[i], 0); + const sumXX = x.reduce((a, b) => a + b * b, 0); + + // Calculate slope and intercept + this.slope = (n * sumXY - sumX * sumY) / (n * sumXX - sumX * sumX); + this.intercept = (sumY - this.slope * sumX) / n; + } + + predict(x) { + return this.slope * x + this.intercept; + } + } + const getProjectCosts = async (req, res) => { + try { + const { id } = req.params; + const { categories, fromDate, toDate } = req.query; + + // Build query + const query = { projectId: id }; + + // Add category filter if provided + if (categories) { + const categoryList = categories.split(','); + query.category = { $in: categoryList }; + } + + // Add date range filter if provided + if (fromDate || toDate) { + query.date = {}; + if (fromDate) query.date.$gte = new Date(fromDate); + if (toDate) query.date.$lte = new Date(toDate); + } + + // Get actual cost data + const costData = await ProjectCostTracking.find(query).sort({ date: 1 }); + + // Process data for response + const result = { + actual: {}, + predicted: {}, + plannedBudget: 10000, // This would typically come from project data + }; + + // Group by category + const categorizedData = {}; + costData.forEach((entry) => { + if (!categorizedData[entry.category]) { + categorizedData[entry.category] = []; + } + categorizedData[entry.category].push({ + date: entry.date, + cost: entry.cost, + }); + }); + + // Calculate cumulative costs for each category + Object.keys(categorizedData).forEach((category) => { + let cumulativeCost = 0; + categorizedData[category] = categorizedData[category].map((item) => { + cumulativeCost += item.cost; + return { + date: item.date, + cost: cumulativeCost, + }; + }); + }); + + // Calculate total costs + const dateMap = new Map(); + + costData.forEach((entry) => { + const dateStr = entry.date.toISOString().split('T')[0]; + if (!dateMap.has(dateStr)) { + dateMap.set(dateStr, { date: entry.date, cost: 0 }); + } + dateMap.get(dateStr).cost += entry.cost; + }); + + // Convert to array and calculate cumulative total + let totalCumulative = 0; + const totalCosts = Array.from(dateMap.values()) + .sort((a, b) => a.date - b.date) + .map((item) => { + totalCumulative += item.cost; + return { + date: item.date, + cost: totalCumulative, + }; + }); + + if (totalCosts.length > 0) { + categorizedData.Total = totalCosts; + } + + // Format actual data + result.actual = categorizedData; + + // Generate prediction data using linear regression + if (costData.length > 0) { + const predictedData = {}; + + // For each category, perform linear regression + Object.keys(categorizedData).forEach((category) => { + if (categorizedData[category].length > 0) { + const categoryData = categorizedData[category]; + + // Get the last actual data point + const lastEntry = categoryData[categoryData.length - 1]; + + // Prepare data for linear regression + const xValues = categoryData.map((item, index) => index); // Use indices as x values + const yValues = categoryData.map((item) => item.cost); + + // Linear regression using ml-regression library + const regression = new SimpleLinearRegression(xValues, yValues); + + // Function to predict value + const predict = (x) => regression.predict(x); + + // Generate predictions for next 3 months + predictedData[category] = []; + + // Get the last date + const lastDate = new Date(lastEntry.date); + const lastValue = lastEntry.cost; + + // Calculate the final predicted value for 3 months ahead + // This ensures we have a perfect linear growth between the last actual point + // and the final prediction point + const finalPredictedValue = predict(xValues.length + 2); // +2 for 3 months ahead (0-indexed) + + // Calculate the monthly growth rate for a perfect straight line + const monthlyGrowth = (finalPredictedValue - lastValue) / 3; + + // Generate predictions for the next 3 months with perfect linear growth + for (let i = 1; i <= 3; i++) { + const predictedDate = new Date(lastDate); + predictedDate.setMonth(lastDate.getMonth() + i); + + // Apply perfect linear growth + const predictedCost = lastValue + monthlyGrowth * i; + + // Ensure predicted value is not less than the last actual value + // This prevents negative growth which doesn't make sense for cumulative costs + const finalCost = Math.max(predictedCost, lastValue); + + predictedData[category].push({ + date: predictedDate, + cost: finalCost, + }); + } + } + }); + + result.predicted = predictedData; + } + + res.status(200).json(result); + } catch (error) { + res.status(500).json({ error: error.message }); + } + }; + + const getAllProjectIds = async (req, res) => { + try { + // Using MongoDB's distinct to get unique project IDs + const projectIds = await ProjectCostTracking.distinct('projectId'); + res.status(200).json({ projectIds }); + } catch (error) { + res.status(500).json({ error: error.message }); + } + }; + + return { + getProjectCosts, + getAllProjectIds, + }; +}; + +module.exports = projectCostTrackingController; diff --git a/src/controllers/dashBoardController.js b/src/controllers/dashBoardController.js index 0cb9415e5..b18b2acaa 100644 --- a/src/controllers/dashBoardController.js +++ b/src/controllers/dashBoardController.js @@ -1,19 +1,11 @@ /* eslint-disable quotes */ -const path = require('path'); -const fs = require('fs/promises'); const mongoose = require('mongoose'); const userProfile = require('../models/userProfile'); -const actionItem = require('../models/actionItem'); const dashboardHelperClosure = require('../helpers/dashboardhelper'); const emailSender = require('../utilities/emailSender'); const AIPrompt = require('../models/weeklySummaryAIPrompt'); const User = require('../models/userProfile'); -// Configuration constants to prevent conflicts -const EMAIL_CONFIG = { - SUPPORT_EMAIL: 'onecommunityglobal@gmail.com', -}; - const dashboardcontroller = function () { const dashboardhelper = dashboardHelperClosure(); const dashboarddata = function (req, res) { @@ -141,20 +133,17 @@ const dashboardcontroller = function () { }); } }) - .catch(error => res.status(400).send(error)); + .catch((error) => res.status(400).send(error)); }; // 6th month and yearly anniversaries const postTrophyIcon = function (req, res) { - console.log("API called with params:", req.params); + console.log('API called with params:', req.params); const userId = mongoose.Types.ObjectId(req.params.userId); const trophyFollowedUp = req.params.trophyFollowedUp === 'true'; - userProfile.findByIdAndUpdate( - userId, - { trophyFollowedUp }, - { new: true } - ) + userProfile + .findByIdAndUpdate(userId, { trophyFollowedUp }, { new: true }) .then((updatedRecord) => { if (!updatedRecord) { return res.status(404).send('No valid records found'); @@ -162,7 +151,7 @@ const dashboardcontroller = function () { res.status(200).send(updatedRecord); }) .catch((error) => { - console.error("Error updating trophy icon:", error); + console.error('Error updating trophy icon:', error); res.status(500).send(error); }); }; @@ -233,7 +222,7 @@ const dashboardcontroller = function () { visual, severity, ); - + try { await emailSender.sendEmail( 'onecommunityglobal@gmail.com', @@ -243,7 +232,7 @@ const dashboardcontroller = function () { ); res.status(200).send('Success'); } catch (error) { - res.status(500).send("Failed to send email"); + res.status(500).send('Failed to send email'); } }; @@ -312,7 +301,7 @@ const dashboardcontroller = function () { ); res.status(200).send('Success'); } catch (error) { - res.status(500).send("Failed to send email"); + res.status(500).send('Failed to send email'); } }; @@ -356,7 +345,7 @@ const dashboardcontroller = function () { } }; const requestFeedbackModal = async function (req, res) { - /** request structure - pass with userId fetched from initial load response. + /** request structure - pass with userId fetched from initial load response. { "haveYouRecievedHelpLastWeek": "Yes", //no @@ -367,7 +356,7 @@ const dashboardcontroller = function () { "daterequestedFeedback": "2025-04-20T04:04:40.189Z", "foundHelpSomeWhereClosePermanently": false, "userId": "5baac381e16814009017678c" - }*/ + } */ try { const savingRequestFeedbackData = await dashboardhelper.requestFeedback(req); return res.status(200).json({ savingRequestFeedbackData }); @@ -375,7 +364,7 @@ const dashboardcontroller = function () { return res.status(500).send({ msg: 'Error occured while fetching data. Please try again!' }); } }; - + const getUserNames = async function (req, res) { /** Call this api once and show in frontend. * this will be the response structure @@ -391,19 +380,19 @@ const dashboardcontroller = function () { */ try { const usersList = await dashboardhelper.getNamesFromProfiles(); - return res.status(200).json({ users : usersList }); + return res.status(200).json({ users: usersList }); } catch (err) { return res.status(500).send({ msg: 'Error occured while fetching data. Please try again!' }); } }; const checkUserFoundHelpSomewhere = async function (req, res) { -/** request structure - pass with userId fetched from initial load response. + /** request structure - pass with userId fetched from initial load response. Only call this api, when clicking found help permanentely { "foundHelpSomeWhereClosePermanently": true, "userId": "5baac381e16814009017678c" -}*/ +} */ try { const foundHelp = await dashboardhelper.checkQuestionaireFeedback(req); return res.status(200).json({ foundHelp }); @@ -413,7 +402,6 @@ const dashboardcontroller = function () { } }; - return { dashboarddata, getAIPrompt, @@ -431,8 +419,8 @@ const dashboardcontroller = function () { postTrophyIcon, requestFeedbackModal, getUserNames, - checkUserFoundHelpSomewhere + checkUserFoundHelpSomewhere, }; }; -module.exports = dashboardcontroller; \ No newline at end of file +module.exports = dashboardcontroller; diff --git a/src/controllers/laborCostController.js b/src/controllers/laborCostController.js index 352c9e0aa..376a55b71 100644 --- a/src/controllers/laborCostController.js +++ b/src/controllers/laborCostController.js @@ -1,4 +1,3 @@ -const mongoose = require('mongoose'); const Labour = require('../models/laborCost'); const createLabourCost = async (req, res) => { @@ -55,15 +54,15 @@ const getLabourCostByDate = async (req, res) => { }; const getLabourCostByProject = async (req, res) => { - const { project_name } = req.query; + const { projectName } = req.query; - if (!project_name) { + if (!projectName) { res.status(500).json({ success: false, message: 'Project Name not provided' }); } try { const filteredDatabyProject = await Labour.find({ - project_name: { $regex: project_name, $options: 'i' }, + project_name: { $regex: projectName, $options: 'i' }, }); res.status(200).json({ success: true, data: filteredDatabyProject }); } catch (error) { diff --git a/src/controllers/titleController.js b/src/controllers/titleController.js index 3724e9808..b29ad1a17 100644 --- a/src/controllers/titleController.js +++ b/src/controllers/titleController.js @@ -1,12 +1,11 @@ -const Team = require('../models/team'); const Project = require('../models/project'); const cacheClosure = require('../utilities/nodeCache'); const userProfileController = require('./userProfileController'); const userProfile = require('../models/userProfile'); -const project = require('../models/project'); +const projectModel = require('../models/project'); -const controller = userProfileController(userProfile, project); -const getAllTeamCodeHelper = controller.getAllTeamCodeHelper; +const controller = userProfileController(userProfile, projectModel); +const { getAllTeamCodeHelper } = controller; const titlecontroller = function (Title) { const cache = cacheClosure(); @@ -122,13 +121,9 @@ const titlecontroller = function (Title) { try { const savedTitle = await title.save(); - - - await userProfile.updateMany( - {}, - { $addToSet: { teamCodes: title.teamCode } } - ); - + + await userProfile.updateMany({}, { $addToSet: { teamCodes: title.teamCode } }); + res.status(200).send(savedTitle); } catch (error) { res.status(500).send(error); @@ -140,7 +135,7 @@ const titlecontroller = function (Title) { const { orderData } = req.body; console.log('Received order data:', orderData); - const updates = await Promise.all( + await Promise.all( orderData.map(async ({ id, order }) => { const updated = await Title.findByIdAndUpdate(id, { order }, { new: true }); console.log('Updated title:', updated); @@ -265,30 +260,6 @@ const titlecontroller = function (Title) { res.status(500).send(error); }); }; - // Update: Confirmed with Jae. Team code is not related to the Team data model. But the team code field within the UserProfile data model. - async function checkTeamCodeExists(teamCode) { - try { - if (cache.getCache('teamCodes')) { - const teamCodes = JSON.parse(cache.getCache('teamCodes')); - return teamCodes.includes(teamCode); - } - const teamCodes = await getAllTeamCodeHelper(); - return teamCodes.includes(teamCode); - } catch (error) { - console.error('Error checking if team code exists:', error); - throw error; - } - } - - async function checkProjectExists(projectID) { - try { - const project = await Project.findOne({ _id: projectID }).exec(); - return !!project; - } catch (error) { - console.error('Error checking if project exists:', error); - throw error; - } - } return { getAllTitles, diff --git a/src/helpers/reporthelper.js b/src/helpers/reporthelper.js index 820633e78..e7101e934 100644 --- a/src/helpers/reporthelper.js +++ b/src/helpers/reporthelper.js @@ -23,7 +23,6 @@ const reporthelper = function () { * @param {integer} endWeekIndex The end week index, eg. 1 for last week. */ const weeklySummaries = async (startWeekIndex, endWeekIndex) => { - const pstStart = moment() .tz('America/Los_Angeles') .startOf('week') diff --git a/src/jobs/dailyMessageEmailNotification.js b/src/jobs/dailyMessageEmailNotification.js index 97a20ff4e..e91ec3688 100644 --- a/src/jobs/dailyMessageEmailNotification.js +++ b/src/jobs/dailyMessageEmailNotification.js @@ -9,44 +9,52 @@ const TEST_MODE = true; // Set to false to disable test mode // Schedule the job to run daily at midnight cron.schedule('0 0 * * *', async () => { - - try { - const userPreferences = await UserPreferences.find().populate('user users.userNotifyingFor'); - - for (const preference of userPreferences) { - const { user, users } = preference; - - let summary = ''; - for (const { userNotifyingFor, notifyEmail } of users) { - if (notifyEmail) { - // Fetch unread messages from the specific sender - const unreadMessages = await Message.find({ - receiver: user._id, - sender: userNotifyingFor._id, - status: { $ne: 'read' }, - }); - - const userNotifyingForProfile = await UserProfile.findById(userNotifyingFor._id).select('firstName lastName'); - - if (unreadMessages.length > 0) { - if (unreadMessages.length > 5) { - summary += `