import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js"; // Helper function to get start and end of day in UTC function getDayRange(date = new Date()) { const start = new Date(date); start.setUTCHours(0, 0, 0, 0); const end = new Date(date); end.setUTCHours(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 parse ISO time string function parseISOTimeString(timeString) { if (!timeString) return new Date(); try { // Parse ISO string directly const parsedDate = new Date(timeString); // Check if the date is valid if (isNaN(parsedDate.getTime())) { throw new Error('Invalid date'); } parsedDate.setMilliseconds(0); console.log("pasdes date" + parsedDate) return parsedDate; } catch (error) { console.error('Error parsing ISO time string:', error); return new Date(); } } // Helper function to check if time difference is less than threshold function isTimeWithinThreshold(receivedTime, currentTime, thresholdSeconds = 10) { const diffMs = Math.abs(currentTime - receivedTime); const diffSeconds = diffMs / 1000; console.log(`Time comparison - Received: ${receivedTime.toISOString()}, Current: ${currentTime.toISOString()}, Diff: ${diffSeconds}s`); return diffSeconds <= thresholdSeconds; } // Helper function to get current UTC time function getCurrentUTCTime() { const now = new Date(); now.setMilliseconds(0); return now; } // function parseTimeToMinutes(timeString) { // if (!timeString) return null; // const [hours, minutes] = timeString.split(':').map(Number); // return hours * 60 + (minutes || 0); // } // function parseDelayToMinutes(delayString) { // if (!delayString) return 0; // if (delayString.includes('h')) { // const hours = parseInt(delayString.replace('h', '').trim()); // return hours * 60; // } else if (delayString.includes('m')) { // return parseInt(delayString.replace('m', '').trim()); // } // return parseInt(delayString) || 0; // Default to minutes if no unit specified // } // function checkScheduleCompliance(currentTime, project, vaStatus, type) { // const warnings = []; // // If project doesn't have schedule or VA is PARTTIME with flexible allowed, no schedule restrictions // if (!project.vaWorkingScheduleStart || !project.vaWorkingScheduleEnd) { // return { isCompliant: true, warnings }; // } // const currentDate = new Date(currentTime); // const currentMinutes = currentDate.getUTCHours() * 60 + currentDate.getUTCMinutes(); // const scheduleStartMinutes = parseTimeToMinutes(project.vaWorkingScheduleStart); // const scheduleEndMinutes = parseTimeToMinutes(project.vaWorkingScheduleEnd); // const allowedStartDelay = parseDelayToMinutes(project.vaWorkingDelayAllowedStart); // const allowedEndDelay = parseDelayToMinutes(project.vaWorkingDelayAllowedEnd); // // Calculate allowed time ranges // const allowedStartMinutes = scheduleStartMinutes - allowedStartDelay; // const allowedEndMinutes = scheduleEndMinutes + allowedEndDelay; // console.log(`Schedule Check - Current: ${currentMinutes}, Start: ${scheduleStartMinutes}, End: ${scheduleEndMinutes}, Allowed Start: ${allowedStartMinutes}, Allowed End: ${allowedEndMinutes}`); // // Check for PARTTIME with flexible schedule // if (vaStatus === "PARTTIME" && project.isFlexibleAllowedPt) { // return { isCompliant: true, warnings }; // } // // For all other cases, check schedule compliance // if (type === "LOGIN") { // // Check if login is too early // if (currentMinutes < allowedStartMinutes) { // warnings.push("early_login"); // } // // Check if login is after shift end // if (currentMinutes > scheduleEndMinutes) { // warnings.push("working_after_shift_end"); // } // // Check if login is within allowed range // // const isWithinAllowedRange = currentMinutes >= allowedStartMinutes && currentMinutes <= allowedEndMinutes; // return { // isCompliant: true, // Allow login regardless of time // warnings // }; // } else if (type === "LOGOUT") { // // Check if logout is too late // if (currentMinutes > allowedEndMinutes) { // warnings.push("late_logout"); // } // // For logout, we're generally more permissive - allow logout even if late // return { // isCompliant: true, // Allow logout regardless of time // warnings // }; // } // return { isCompliant: true, warnings }; // } // function to close open work tracks and create logout async function closePreviousSession(vaId, lastLog, logTime = new Date()) { try { const currentTime = logTime; // Find all open work tracks for this VA and project (where to is not set or totalTime is 0) const openWorkTracks = await WorkTrackModel.find({ vaId, logId: lastLog._id, $or: [ { to: { $exists: false } }, { totalTime: 0 }, { to: null } ] }); // console.log(`Found ${openWorkTracks.length} open work tracks to close`); // Close all open work tracks for (const workTrack of openWorkTracks) { const fromTime = new Date(workTrack.from); const totalTimeMs = currentTime - fromTime; // Update the work track with end time and total time workTrack.to = currentTime; workTrack.totalTime = totalTimeMs; await workTrack.save(); // console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`); } // Create a logout log entry const logoutLog = await LoggedModel.create({ loggedType: "LOGOUT", vaId, vaEmail: lastLog.vaEmail, // Use email from previous log projectId: lastLog.projectId, time: currentTime // Use same timestamp for consistent logging }); // console.log(`Created logout log: ${logoutLog._id}`); try { const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; const sfDataSync = { "_id": logoutLog._id, "loggedType": "LOGOUT", "vaId": vaId, "vaEmail": lastLog.vaEmail, "projectId": lastLog.projectId, "time": logoutLog.time, } const response = await fetch(salesforceUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // Add any required headers for Salesforce authentication // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN }, body: JSON.stringify(sfDataSync) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); } const result = await response.json(); // console.log("✅ Successfully synced VA data to Salesforce:", result); return { closedWorkTracks: openWorkTracks.length, logoutLogId: logoutLog._id, SyncSalesforce: result }; } catch (error) { console.error("Failed to sync with Salesforce:", error.message); console.log(error) // Create a custom error to distinguish Salesforce errors const salesforceError = new Error(`Salesforce sync failed: ${error.message}`); salesforceError.isSalesforceError = true; throw salesforceError; } } catch (error) { console.error("Error closing previous session:", error); throw error; } } // 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, time: receivedTimeString } = payload; // console.log("Received payload:", payload); if (!vaId || !vaEmail || !projectId || !type) { return res.status(400).send("Missing required properties"); } // Determine the log time based on received time (both in UTC) const currentTime = getCurrentUTCTime(); // Current UTC time let logTime = currentTime; if (receivedTimeString) { // Parse the received ISO time string const receivedTime = parseISOTimeString(receivedTimeString); // Check if the difference is less than 10 seconds if (isTimeWithinThreshold(receivedTime, currentTime, 10)) { logTime = receivedTime; console.log(`✅ Using received ISO time: ${receivedTimeString}, difference is within 10 seconds`); console.log(`✅ Parsed as: ${receivedTime.toISOString()}`); } else { logTime = currentTime; console.log(`⚠️ Using current UTC time, received time difference exceeds 10 seconds.`); console.log(`Received: ${receivedTimeString} (parsed: ${receivedTime.toISOString()})`); console.log(`Current: ${currentTime.toISOString()}`); } } const project = await ProjectModel.findById(projectId); if (!project) { return res.status(400).json({ success: false, message: "Project not found" }); } const vaUser = await VAModel.findById(vaId); if (!vaUser) { return res.status(400).json({ success: false, message: "VA not found" }); } // Check schedule compliance // const scheduleCheck = checkScheduleCompliance(logTime, project, vaUser.status, type); // console.log(`Schedule compliance check:`, scheduleCheck); // if (!scheduleCheck.isCompliant) { // return res.status(400).json({ // success: false, // message: `Cannot ${type.toLowerCase()} - outside of allowed schedule time`, // warnings: scheduleCheck.warnings // }); // } // For LOGIN requests, check daily hours (using UTC day range) if (type === "LOGIN") { const { start, end } = getDayRange(); // UTC day range // 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)})`, // warnings: scheduleCheck.warnings }); } // Check last log entry const lastLog = await LoggedModel.findOne({ vaId }) .sort({ time: -1 }) .lean(); // Prevent login if last action was login (not logout) if (lastLog && lastLog.loggedType === "LOGIN") { // check last workModel login time and make a logout request then create login // console.log(`User already logged in. Closing previous session...`); const closeResult = await closePreviousSession(vaId, lastLog, logTime); // console.log(`Previous session closed: ${closeResult.closedWorkTracks} work tracks, logout log: ${closeResult.logoutLogId}`); // Now proceed to create new login const createdLog = await LoggedModel.create({ loggedType: "LOGIN", vaId, vaEmail, projectId, time: logTime }); try { const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; const sfData = { "_id": createdLog._id, "loggedType": "LOGIN", "vaId": vaId, "vaEmail": vaEmail, "projectId": projectId, "time": createdLog.time, } const response = await fetch(salesforceUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // Add any required headers for Salesforce authentication // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN }, body: JSON.stringify(sfData) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); } const result = await response.json(); // console.log("✅ Successfully synced VA data to Salesforce:", result); res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result, previousSession: { closedWorkTracks: closeResult.closedWorkTracks, logoutLogId: closeResult.logoutLogId }, timeUsed: receivedTimeString ? 'received' : 'current', actualTime: createdLog.time, // warnings: scheduleCheck.warnings }); } catch (error) { console.error("Failed to sync with Salesforce:", error.message); console.log(error) // Create a custom error to distinguish Salesforce errors const salesforceError = new Error(`Salesforce sync failed: ${error.message}`); salesforceError.isSalesforceError = true; throw salesforceError; } } } const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId, time: logTime }); try { const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; const sfData = { "_id": createdLog._id, "loggedType": type, "vaId": vaId, "vaEmail": vaEmail, "projectId": projectId, "time": createdLog.time, } const response = await fetch(salesforceUrl, { method: 'POST', headers: { 'Content-Type': 'application/json', // Add any required headers for Salesforce authentication // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN }, body: JSON.stringify(sfData) }); if (!response.ok) { const errorText = await response.text(); throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); } const result = await response.json(); // console.log("✅ Successfully synced VA data to Salesforce:", result); res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result, timeUsed: receivedTimeString ? 'received' : 'current', actualTime: createdLog.time, // warnings: scheduleCheck.warnings }); } catch (error) { console.error("Failed to sync with Salesforce:", error.message); console.log(error) // Create a custom error to distinguish Salesforce errors const salesforceError = new Error(`Salesforce sync failed: ${error.message}`); salesforceError.isSalesforceError = true; throw salesforceError; } } catch (error) { console.error("Error in va_project_log_callback:", error); return res.status(500).send("Internal Server Error"); } } export { va_project_log_callback };