From d50c18856f45e72df23f72c3c52db37ff211eb31 Mon Sep 17 00:00:00 2001 From: Aakash Date: Tue, 18 Nov 2025 17:15:11 +0530 Subject: [PATCH] Updated track log api to auto logout project after completing dedicated hours --- .../va/vaLog/vaLogTrackWork.controller.js | 124 +++++++++++++++++- .../va/vaProjects/vaProjectLog.controller.js | 73 ++++++++++- .../vaProjects/vaProjectLogList.controller.js | 19 ++- src/models/vaModel.js | 2 +- 4 files changed, 211 insertions(+), 7 deletions(-) diff --git a/src/controllers/va/vaLog/vaLogTrackWork.controller.js b/src/controllers/va/vaLog/vaLogTrackWork.controller.js index 97bd382..89366f6 100644 --- a/src/controllers/va/vaLog/vaLogTrackWork.controller.js +++ b/src/controllers/va/vaLog/vaLogTrackWork.controller.js @@ -1,6 +1,91 @@ import jwt from "jsonwebtoken"; 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 async function va_log_track_work_callback(req, res) { try { @@ -50,6 +135,16 @@ async function va_log_track_work_callback(req, res) { // Handle POST → create entry 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({ vaId, projectId, @@ -63,6 +158,11 @@ async function va_log_track_work_callback(req, res) { success: true, message: "Work tracking started", 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`; + // 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.totalTime = totalTimeMs; // store as number of minutes await workEntry.save(); - return res.status(200).json({ + const response = { success: true, message: "Work tracking stopped", data: { @@ -101,9 +204,24 @@ async function va_log_track_work_callback(req, res) { from: workEntry.from, to: workEntry.to, 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 diff --git a/src/controllers/va/vaProjects/vaProjectLog.controller.js b/src/controllers/va/vaProjects/vaProjectLog.controller.js index ba34201..54d0d73 100644 --- a/src/controllers/va/vaProjects/vaProjectLog.controller.js +++ b/src/controllers/va/vaProjects/vaProjectLog.controller.js @@ -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 async function va_project_log_callback(req, res) { try { const payload = req.body || {}; const { vaId, vaEmail, projectId, type } = payload; - + 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 }) return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); diff --git a/src/controllers/va/vaProjects/vaProjectLogList.controller.js b/src/controllers/va/vaProjects/vaProjectLogList.controller.js index bd7e541..13bafb5 100644 --- a/src/controllers/va/vaProjects/vaProjectLogList.controller.js +++ b/src/controllers/va/vaProjects/vaProjectLogList.controller.js @@ -9,6 +9,21 @@ function formatHMS(ms) { 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) { try { const { projectId, from, to } = req.params; @@ -101,7 +116,9 @@ async function va_projects_log_list_function(req, res) { activityStatus, totalWorkTime: formatHMS(totalWorkMs), totalWorkMs, - totalLogs: logs.length + dedicatedHours: formatHoursToReadable(project?.dedicatedHours), + totalLogs: logs.length, + status: project?.status }); } diff --git a/src/models/vaModel.js b/src/models/vaModel.js index d486ce2..4747eda 100644 --- a/src/models/vaModel.js +++ b/src/models/vaModel.js @@ -91,7 +91,7 @@ const projectSchema = new Schema({ }, ], dedicatedHours: { - type: Number, + type: String, default: 0 }, status: {