Skip to content

Commit

Permalink
Merge pull request #1174 from OneCommunityGlobal/development
Browse files Browse the repository at this point in the history
Backend Release to Main [2.07]
  • Loading branch information
one-community authored Dec 9, 2024
2 parents f094262 + 25a7f38 commit 3ac7117
Show file tree
Hide file tree
Showing 12 changed files with 532 additions and 461 deletions.
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');
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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());
Expand Down Expand Up @@ -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
*/
Expand Down Expand Up @@ -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',
Expand All @@ -1217,7 +1298,7 @@ const timeEntrycontroller = function (TimeEntry) {
isActive: { $ne: false },
},
' -createdDateTime',
)
).lean()
.populate('teamId')
.sort({ lastModifiedDateTime: -1 })
.then((results) => {
Expand All @@ -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);
Expand Down Expand Up @@ -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();

Expand All @@ -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
*/
Expand Down Expand Up @@ -1441,9 +1567,12 @@ const timeEntrycontroller = function (TimeEntry) {
getLostTimeEntriesForTeamList,
backupHoursByCategoryAllUsers,
backupIntangibleHrsAllUsers,
recalculateHoursByCategoryAllUsers,
recalculateIntangibleHrsAllUsers,
getTimeEntriesForReports,
getTimeEntriesForProjectReports,
getTimeEntriesForPeopleReports,
startRecalculation,
checkRecalculationStatus,
};
};

Expand Down
Loading

0 comments on commit 3ac7117

Please sign in to comment.