Updated track log api to auto logout project after completing dedicated hours

This commit is contained in:
Aakash Mittal 2025-11-18 17:15:11 +05:30
parent 4a2734cf2c
commit d50c18856f
4 changed files with 211 additions and 7 deletions

View File

@ -1,6 +1,91 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { WorkTrackModel, VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js"; import { WorkTrackModel, VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
// Helper function to get start and end of day
function getDayRange(date = new Date()) {
const start = new Date(date);
start.setHours(0, 0, 0, 0);
const end = new Date(date);
end.setHours(23, 59, 59, 999);
return { start, end };
}
function formatHoursToReadable(hours) {
const wholeHours = Math.floor(hours);
const decimalMinutes = (hours - wholeHours) * 60;
const wholeMinutes = Math.round(decimalMinutes);
if (wholeHours > 0 && wholeMinutes > 0) {
return `${wholeHours}h ${wholeMinutes}m`;
} else if (wholeHours > 0) {
return `${wholeHours}h`;
} else {
return `${wholeMinutes}m`;
}
}
// Helper function to check daily hours and create logout log if needed
async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, currentSessionId = null, additionalTimeMs = 0) {
try {
const today = new Date();
const { start, end } = getDayRange(today);
// Get today's work entries for this VA and project
// Exclude the current session if it's already in the database
const query = {
vaId,
projectId,
from: { $gte: start, $lte: end }
};
if (currentSessionId) {
query._id = { $ne: currentSessionId };
}
const todayWorkEntries = await WorkTrackModel.find(query);
// Calculate total hours worked today
let totalTodayMs = todayWorkEntries.reduce((total, entry) => {
return total + (entry.totalTime || 0);
}, 0);
// Add the additional time from current session if provided
totalTodayMs += additionalTimeMs;
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
// Check if daily dedicated hours are completed
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
// Create automatic logout log
const logoutLog = await LoggedModel.create({
loggedType: "LOGOUT",
vaId,
vaEmail,
projectId
});
return {
shouldLogout: true,
totalTodayHours: totalTodayHours,
dedicatedHours: project.dedicatedHours,
logoutLogId: logoutLog._id
};
}
return {
shouldLogout: false,
totalTodayHours: totalTodayHours,
dedicatedHours: project.dedicatedHours
};
} catch (error) {
console.error("Error in checkDailyHoursAndLogout:", error);
return { shouldLogout: false, error: error.message };
}
}
// Getting logged hours in a specific project of VA // Getting logged hours in a specific project of VA
async function va_log_track_work_callback(req, res) { async function va_log_track_work_callback(req, res) {
try { try {
@ -50,6 +135,16 @@ async function va_log_track_work_callback(req, res) {
// Handle POST → create entry // Handle POST → create entry
if (req.method === "POST") { if (req.method === "POST") {
const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project);
if (dailyCheck.shouldLogout) {
return res.status(400).json({
success: false,
message: `Cannot start work - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`,
autoLogout: true
});
}
const createdTrack = await WorkTrackModel.create({ const createdTrack = await WorkTrackModel.create({
vaId, vaId,
projectId, projectId,
@ -63,6 +158,11 @@ async function va_log_track_work_callback(req, res) {
success: true, success: true,
message: "Work tracking started", message: "Work tracking started",
data: createdTrack, data: createdTrack,
dailyHours: {
todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
remaining: formatHoursToReadable(dailyCheck.dedicatedHours - dailyCheck.totalTodayHours)
}
}); });
} }
@ -89,11 +189,14 @@ async function va_log_track_work_callback(req, res) {
const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`; const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`;
// Check if daily dedicated hours are completed after this work session
const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, id, totalTimeMs);
workEntry.to = toTime; workEntry.to = toTime;
workEntry.totalTime = totalTimeMs; // store as number of minutes workEntry.totalTime = totalTimeMs; // store as number of minutes
await workEntry.save(); await workEntry.save();
return res.status(200).json({ const response = {
success: true, success: true,
message: "Work tracking stopped", message: "Work tracking stopped",
data: { data: {
@ -101,9 +204,24 @@ async function va_log_track_work_callback(req, res) {
from: workEntry.from, from: workEntry.from,
to: workEntry.to, to: workEntry.to,
formattedTotalTime, formattedTotalTime,
totalTimeMs totalTimeMs,
sessionHours: totalTimeMs / (1000 * 60 * 60)
}, },
}); dailyHours: {
todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
remaining: formatHoursToReadable(Math.max(0, dailyCheck.dedicatedHours - dailyCheck.totalTodayHours))
}
};
// If auto-logout was triggered, add that info to response
if (dailyCheck.shouldLogout) {
response.autoLogout = true;
response.message = `Work tracking stopped. Auto-logout created - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`;
response.logoutLogId = dailyCheck.logoutLogId;
}
return res.status(200).json(response);
} }
// Fallback for unsupported methods // Fallback for unsupported methods

View File

@ -1,12 +1,81 @@
import { LoggedModel } from "../../../models/vaModel.js"; import { LoggedModel, WorkTrackModel, ProjectModel } from "../../../models/vaModel.js";
// Helper function to get start and end of day
function getDayRange(date = new Date()) {
const start = new Date(date);
start.setHours(0, 0, 0, 0);
const end = new Date(date);
end.setHours(23, 59, 59, 999);
return { start, end };
}
function formatHoursToReadable(hours) {
const wholeHours = Math.floor(hours);
const decimalMinutes = (hours - wholeHours) * 60;
const wholeMinutes = Math.round(decimalMinutes);
if (wholeHours > 0 && wholeMinutes > 0) {
return `${wholeHours}h ${wholeMinutes}m`;
} else if (wholeHours > 0) {
return `${wholeHours}h`;
} else {
return `${wholeMinutes}m`;
}
}
// Getting logged hours in a specific project of VA // Getting logged hours in a specific project of VA
async function va_project_log_callback(req, res) { async function va_project_log_callback(req, res) {
try { try {
const payload = req.body || {}; const payload = req.body || {};
const { vaId, vaEmail, projectId, type } = payload; const { vaId, vaEmail, projectId, type } = payload;
if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props"); if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props");
const project = await ProjectModel.findById(projectId);
if (!project) {
return res.status(400).json({ success: false, message: "Project not found" });
}
// For LOGIN requests, check daily hours
if (type === "LOGIN") {
const { start, end } = getDayRange();
// Get today's total hours
const todayWorkEntries = await WorkTrackModel.find({
vaId,
projectId,
from: { $gte: start, $lte: end }
});
const totalTodayMs = todayWorkEntries.reduce((total, entry) => {
return total + (entry.totalTime || 0);
}, 0);
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
// Check if daily hours are already completed
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
return res.status(400).json({
success: false,
message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours)})`
});
}
// Check last log entry
const lastLog = await LoggedModel.findOne({ vaId, projectId })
.sort({ time: -1 })
.lean();
// Prevent login if last action was login (not logout)
if (lastLog && lastLog.loggedType === "LOGIN") {
return res.status(400).json({
success: false,
message: "User is already logged in to this project"
});
}
}
const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id });

View File

@ -9,6 +9,21 @@ function formatHMS(ms) {
return `${h}h ${m}m ${s}s`; return `${h}h ${m}m ${s}s`;
} }
function formatHoursToReadable(hours) {
const wholeHours = Math.floor(hours);
const decimalMinutes = (hours - wholeHours) * 60;
const wholeMinutes = Math.round(decimalMinutes);
if (wholeHours > 0 && wholeMinutes > 0) {
return `${wholeHours}h ${wholeMinutes}m`;
} else if (wholeHours > 0) {
return `${wholeHours}h`;
} else {
return `${wholeMinutes}m`;
}
}
async function va_projects_log_list_function(req, res) { async function va_projects_log_list_function(req, res) {
try { try {
const { projectId, from, to } = req.params; const { projectId, from, to } = req.params;
@ -101,7 +116,9 @@ async function va_projects_log_list_function(req, res) {
activityStatus, activityStatus,
totalWorkTime: formatHMS(totalWorkMs), totalWorkTime: formatHMS(totalWorkMs),
totalWorkMs, totalWorkMs,
totalLogs: logs.length dedicatedHours: formatHoursToReadable(project?.dedicatedHours),
totalLogs: logs.length,
status: project?.status
}); });
} }

View File

@ -91,7 +91,7 @@ const projectSchema = new Schema({
}, },
], ],
dedicatedHours: { dedicatedHours: {
type: Number, type: String,
default: 0 default: 0
}, },
status: { status: {