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 { const accessToken = req.headers.token; if (!accessToken) { return res.status(401).json({ success: false, message: "Missing token" }); } const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN); if (!decoded || !decoded.email) { return res.status(401).json({ success: false, message: "Invalid token" }); } const vaEmail = decoded.email; const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); if (!vaUser) { return res.status(404).json({ success: false, message: "VA not found" }); } const vaId = vaUser._id; const payload = req.body || {}; const { projectId, logId, title, appName } = payload; const project = await ProjectModel.findById(projectId); if (!project) return res.status(400).json({ success: false, message: "Project not found" }); const logEntry = await LoggedModel.findById(logId); if (!logEntry) return res.status(400).json({ success: false, message: "Log entry not found" }); // Check last log entry for this VA and project const lastLog = await LoggedModel.findOne({ vaId, projectId }) .sort({ time: -1 }) // get the most recent log .lean(); // If the last log is a LOGOUT, prevent new work tracking if (lastLog.loggedType === "LOGOUT") { return res.status(400).json({ success: false, message: "User has already logged out from this project", }); } if (!projectId || !logId || !title || !appName) { return res.status(400).json({ success: false, message: "Missing Props" }); } // 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, logId, title, appName, from: Date.now(), }); return res.status(201).json({ success: true, message: "Work tracking started", data: createdTrack, dailyHours: { todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours), dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours), remaining: formatHoursToReadable(dailyCheck.dedicatedHours - dailyCheck.totalTodayHours) } }); } // Handle PATCH → update entry (add 'to' and calculate total time) if (req.method === "PATCH") { const { id } = payload; // expecting the workTrack record ID to update if (!id) { return res.status(400).json({ success: false, message: "Missing workTrack ID" }); } const workEntry = await WorkTrackModel.findById(id); if (!workEntry) { return res.status(404).json({ success: false, message: "Work entry not found" }); } const toTime = new Date(); const fromTime = new Date(workEntry.from); const totalTimeMs = toTime - fromTime; // Optional: store totalTime in minutes or hours instead of raw ms const hours = Math.floor(totalTimeMs / (1000 * 60 * 60)); const minutes = Math.floor((totalTimeMs % (1000 * 60 * 60)) / (1000 * 60)); const seconds = Math.floor((totalTimeMs % (1000 * 60)) / 1000); 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(); const response = { success: true, message: "Work tracking stopped", data: { id: workEntry._id, from: workEntry.from, to: workEntry.to, formattedTotalTime, 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 return res.status(405).json({ success: false, message: "Method not allowed" }); } catch (error) { return res.status(500).send("Internal Server Error"); } } export { va_log_track_work_callback };