import jwt from "jsonwebtoken"; import { VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js"; async function va_timesheet_summary_function(req, res) { try { const sf_token = req.headers.sf_token; let vaId; let vaUser; let vaEmail; if(sf_token) { vaId = req.body.vaId; vaUser = await VAModel.findById(vaId); if (!vaUser) { return res.status(404).json({ success: false, message: "VA not found" }); } vaEmail = vaUser.userDetails.email } else { 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" }); } vaEmail = decoded.email; vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); if (!vaUser) { return res.status(404).json({ success: false, message: "VA not found" }); } vaId = vaUser._id; } const { projectId, date } = req.params; if (!projectId || !date) { return res.status(400).json({ success: false, message: "Project ID and date are required" }); } // Validate project exists and VA is assigned to it const project = await ProjectModel.findOne({ _id: projectId, "vaAssigned.vaId": vaId }); if (!project) { return res.status(404).json({ success: false, message: "Project not found or VA not assigned to this project" }); } // Calculate date range for the specific day let targetDate; try { // Try parsing as ISO date (YYYY-MM-DD) targetDate = new Date(date); if (isNaN(targetDate.getTime())) { // Try parsing as YYYY-MM-DD format specifically const [year, month, day] = date.split('-'); targetDate = new Date(parseInt(year), parseInt(month) - 1, parseInt(day)); } if (isNaN(targetDate.getTime())) { return res.status(400).json({ success: false, message: "Invalid date format. Use YYYY-MM-DD" }); } } catch (error) { return res.status(400).json({ success: false, message: "Invalid date format" }); } const startOfDay = new Date(targetDate); startOfDay.setHours(0, 0, 0, 0); const endOfDay = new Date(targetDate); endOfDay.setHours(23, 59, 59, 999); // Get all logs for this VA, project, and date const logs = await LoggedModel.find({ vaId, projectId, time: { $gte: startOfDay, $lte: endOfDay } }).sort({ time: 1 }).lean(); // Calculate work sessions and totals let totalWorkSeconds = 0; let firstLoginTime = null; let lastLogoutTime = null; const sessions = []; const loginStack = []; for (const log of logs) { const logTime = new Date(log.time); if (log.loggedType === "LOGIN") { loginStack.push(logTime); // Set first login time if (!firstLoginTime || logTime < firstLoginTime) { firstLoginTime = logTime; } } else if (log.loggedType === "LOGOUT") { if (loginStack.length > 0) { // Pop the last login from stack const lastLogin = loginStack.pop(); const sessionSeconds = (logTime - lastLogin) / 1000; totalWorkSeconds += sessionSeconds; // Set last logout time if (!lastLogoutTime || logTime > lastLogoutTime) { lastLogoutTime = logTime; } sessions.push({ login: lastLogin, logout: logTime, durationSeconds: sessionSeconds }); } else { console.log(`Orphaned LOGOUT at ${logTime.toISOString()} - no matching LOGIN`); } } } // Handle open session (LOGIN without LOGOUT) if (loginStack.length > 0) { for (const lastLogin of loginStack) { const sessionSeconds = (endOfDay - lastLogin) / 1000; totalWorkSeconds += sessionSeconds; sessions.push({ login: lastLogin, logout: null, durationSeconds: sessionSeconds, isOpen: true }); } } // Convert seconds to time format const formatTime = (seconds) => { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); return `${h.toString().padStart(2, "0")}:${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; }; const formatTimeDetailed = (seconds) => { const h = Math.floor(seconds / 3600); const m = Math.floor((seconds % 3600) / 60); const s = Math.floor(seconds % 60); if (h > 0) { return `${h}h ${m}m ${s}s`; } else if (m > 0) { return `${m}m ${s}s`; } else { return `${s}s`; } }; // Calculate dedicated hours and pending hours const dedicatedHours = project.dedicatedHours || 0; const totalWorkedHours = totalWorkSeconds / 3600; // Convert to hours const pendingHours = Math.max(0, dedicatedHours - totalWorkedHours); const response = { success: true, data: { project: { id: project._id, name: project.name, dedicatedHours: dedicatedHours }, va: { id: vaUser._id, name: vaUser.userDetails?.name || 'Unknown', email: vaEmail }, summary: { totalWorkedHours: formatTime(totalWorkSeconds), totalWorkedHoursDetailed: formatTimeDetailed(totalWorkSeconds), totalWorkedHoursDecimal: totalWorkedHours.toFixed(2), totalWorkedMinutes: (totalWorkSeconds / 60).toFixed(2), dedicatedHours: dedicatedHours, pendingHours: pendingHours.toFixed(2), firstLoginTime: firstLoginTime ? firstLoginTime.toISOString() : null, lastLogoutTime: lastLogoutTime ? lastLogoutTime.toISOString() : null }, sessions: sessions.map(session => ({ login: session.login.toISOString(), logout: session.logout ? session.logout.toISOString() : null, duration: formatTime(session.durationSeconds), durationDetailed: formatTimeDetailed(session.durationSeconds), durationSeconds: session.durationSeconds, isOpen: session.isOpen || false })), } }; return res.status(200).json(response); } catch (error) { console.error("Timesheet summary error:", error); return res.status(500).json({ success: false, message: "Internal Server Error" }); } } export { va_timesheet_summary_function };