Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Newell - 🔥 leaderboard problem with dots not showing green when a person has finished their hours #1173

Merged
merged 1 commit into from
Dec 9, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 159 additions & 30 deletions src/controllers/timeEntryController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
const moment = require('moment-timezone');
const mongoose = require('mongoose');
const { v4: uuidv4 } = require('uuid');
const logger = require('../startup/logger');
const UserProfile = require('../models/userProfile');
const Project = require('../models/project');
@@ -419,7 +420,7 @@ const addEditHistory = async (
<p>One Community</p>
<!-- Adding multiple non-breaking spaces -->
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
<hr style="border-top: 1px dashed #000;"/>
<hr style="border-top: 1px dashed #000;"/>
<p><b>ADMINISTRATIVE DETAILS:</b></p>
<p><b>Start Date:</b> ${moment(userprofile.startDate).utc().format('M-D-YYYY')}</p>
<p><b>Role:</b> ${userprofile.role}</p>
@@ -593,7 +594,7 @@ const timeEntrycontroller = function (TimeEntry) {

await timeEntry.save({ session });
if (userprofile) {
await userprofile.save({ session });
await userprofile.save({ session, validateModifiedOnly: true });
// since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time
removeOutdatedUserprofileCache(userprofile._id.toString());
}
@@ -866,7 +867,7 @@ const timeEntrycontroller = function (TimeEntry) {
}
await timeEntry.save({ session });
if (userprofile) {
await userprofile.save({ session });
await userprofile.save({ session, validateModifiedOnly: true });

// since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time
removeOutdatedUserprofileCache(userprofile._id.toString());
@@ -939,7 +940,7 @@ const timeEntrycontroller = function (TimeEntry) {

await timeEntry.remove({ session });
if (userprofile) {
await userprofile.save({ session });
await userprofile.save({ session, validateModifiedOnly: true });

// since userprofile is updated, need to remove the cache so that the updated userprofile is fetched next time
removeOutdatedUserprofileCache(userprofile._id.toString());
@@ -1062,40 +1063,115 @@ const timeEntrycontroller = function (TimeEntry) {
});
};

const getTimeEntriesForReports = function (req, res) {
const getTimeEntriesForReports =async function (req, res) {
const { users, fromDate, toDate } = req.body;
const cacheKey = `timeEntry_${fromDate}_${toDate}`;
const timeentryCache=cacheClosure();
const cacheData=timeentryCache.hasCache(cacheKey)
if(cacheData){
const data = timeentryCache.getCache(cacheKey);
return res.status(200).send(data);
}
try {
const results = await TimeEntry.find(
{
personId: { $in: users },
dateOfWork: { $gte: fromDate, $lte: toDate },
},
'-createdDateTime' // Exclude unnecessary fields
)
.lean() // Returns plain JavaScript objects, not Mongoose documents
.populate({
path: 'projectId',
select: '_id projectName', // Only return necessary fields from the project
})
.exec(); // Executes the query
const data = results.map(element => {
const record = {
_id: element._id,
isTangible: element.isTangible,
personId: element.personId,
dateOfWork: element.dateOfWork,
hours: formatSeconds(element.totalSeconds)[0],
minutes: formatSeconds(element.totalSeconds)[1],
projectId: element.projectId?._id || '',
projectName: element.projectId?.projectName || '',
};
return record;
});
timeentryCache.setCache(cacheKey,data);
return res.status(200).send(data);
} catch (error) {
res.status(400).send(error);
}
};

const getTimeEntriesForProjectReports = function (req, res) {
const { users, fromDate, toDate } = req.body;

// Fetch only necessary fields and avoid bringing the entire document
TimeEntry.find(
{
personId: { $in: users },
dateOfWork: { $gte: fromDate, $lte: toDate },
},
' -createdDateTime',
'totalSeconds isTangible dateOfWork projectId',
)
.populate('projectId')

.populate('projectId', 'projectName _id')
.lean() // lean() for better performance as we don't need Mongoose document methods
.then((results) => {
const data = [];

results.forEach((element) => {
const record = {};
record._id = element._id;
record.isTangible = element.isTangible;
record.personId = element.personId._id;
record.dateOfWork = element.dateOfWork;
const data = results.map((element) => {
const record = {
isTangible: element.isTangible,
dateOfWork: element.dateOfWork,
projectId: element.projectId ? element.projectId._id : '',
projectName: element.projectId ? element.projectId.projectName : '',
};

// Convert totalSeconds to hours and minutes
[record.hours, record.minutes] = formatSeconds(element.totalSeconds);
record.projectId = element.projectId ? element.projectId._id : '';
record.projectName = element.projectId ? element.projectId.projectName : '';
data.push(record);

return record;
});

res.status(200).send(data);
})
.catch((error) => {
res.status(400).send(error);
res.status(400).send({ message: 'Error fetching time entries for project reports', error });
});
};

const getTimeEntriesForPeopleReports = async function (req, res) {
try {
const { users, fromDate, toDate } = req.body;

const results = await TimeEntry.find(
{
personId: { $in: users },
dateOfWork: { $gte: fromDate, $lte: toDate },
},
'personId totalSeconds isTangible dateOfWork',
).lean(); // Use lean() for better performance

const data = results
.map((entry) => {
const [hours, minutes] = formatSeconds(entry.totalSeconds);
return {
personId: entry.personId,
hours,
minutes,
isTangible: entry.isTangible,
dateOfWork: entry.dateOfWork,
};
})
.filter(Boolean);

res.status(200).send(data);
} catch (error) {
res.status(400).send({ message: 'Error fetching time entries for people reports', error });
}
};

/**
* Get time entries for a specified project
*/
@@ -1208,7 +1284,12 @@ const timeEntrycontroller = function (TimeEntry) {
*/
const getLostTimeEntriesForTeamList = function (req, res) {
const { teams, fromDate, toDate } = req.body;

const lostteamentryCache=cacheClosure()
const cacheKey = `LostTeamEntry_${fromDate}_${toDate}`;
const cacheData=lostteamentryCache.getCache(cacheKey)
if(cacheData){
return res.status(200).send(cacheData)
}
TimeEntry.find(
{
entryType: 'team',
@@ -1217,7 +1298,7 @@ const timeEntrycontroller = function (TimeEntry) {
isActive: { $ne: false },
},
' -createdDateTime',
)
).lean()
.populate('teamId')
.sort({ lastModifiedDateTime: -1 })
.then((results) => {
@@ -1234,7 +1315,8 @@ const timeEntrycontroller = function (TimeEntry) {
[record.hours, record.minutes] = formatSeconds(element.totalSeconds);
data.push(record);
});
res.status(200).send(data);
lostteamentryCache.setCache(cacheKey,data);
return res.status(200).send(data);
})
.catch((error) => {
res.status(400).send(error);
@@ -1367,10 +1449,12 @@ const timeEntrycontroller = function (TimeEntry) {
return newTotalIntangibleHrs;
};

const recalculationTaskQueue = [];

/**
* recalculate the hoursByCatefory for all users and update the field
* recalculate the hoursByCategory for all users and update the field
*/
const recalculateHoursByCategoryAllUsers = async function (req, res) {
const recalculateHoursByCategoryAllUsers = async function (taskId) {
const session = await mongoose.startSession();
session.startTransaction();

@@ -1385,18 +1469,60 @@ const timeEntrycontroller = function (TimeEntry) {
await Promise.all(recalculationPromises);

await session.commitTransaction();
return res.status(200).send({
message: 'finished the recalculation for hoursByCategory for all users',
});

const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId);
if (recalculationTask) {
recalculationTask.status = 'Completed';
recalculationTask.completionTime = new Date().toISOString();
}
} catch (err) {
await session.abortTransaction();
const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId);
if (recalculationTask) {
recalculationTask.status = 'Failed';
recalculationTask.completionTime = new Date().toISOString();
}

logger.logException(err);
return res.status(500).send({ error: err.toString() });
} finally {
session.endSession();
}
};

const startRecalculation = async function (req, res) {
const taskId = uuidv4();
recalculationTaskQueue.push({
taskId,
status: 'In progress',
startTime: new Date().toISOString(),
completionTime: null,
});
if (recalculationTaskQueue.length > 10) {
recalculationTaskQueue.shift();
}

res.status(200).send({
message: 'The recalculation task started in the background',
taskId,
});

setTimeout(() => recalculateHoursByCategoryAllUsers(taskId), 0);
};

const checkRecalculationStatus = async function (req, res) {
const { taskId } = req.params;
const recalculationTask = recalculationTaskQueue.find((task) => task.taskId === taskId);
if (recalculationTask) {
res.status(200).send({
status: recalculationTask.status,
startTime: recalculationTask.startTime,
completionTime: recalculationTask.completionTime,
});
} else {
res.status(404).send({ message: 'Task not found' });
}
};

/**
* recalculate the totalIntangibleHrs for all users and update the field
*/
@@ -1441,9 +1567,12 @@ const timeEntrycontroller = function (TimeEntry) {
getLostTimeEntriesForTeamList,
backupHoursByCategoryAllUsers,
backupIntangibleHrsAllUsers,
recalculateHoursByCategoryAllUsers,
recalculateIntangibleHrsAllUsers,
getTimeEntriesForReports,
getTimeEntriesForProjectReports,
getTimeEntriesForPeopleReports,
startRecalculation,
checkRecalculationStatus,
};
};

Loading