302 lines
12 KiB
JavaScript
302 lines
12 KiB
JavaScript
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
|
|
// });
|
|
|
|
// 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": vaEmail,
|
|
// "projectId": 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 {
|
|
// shouldLogout: true,
|
|
// totalTodayHours: totalTodayHours,
|
|
// dedicatedHours: project.dedicatedHours,
|
|
// 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;
|
|
// }
|
|
// }
|
|
|
|
// 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 || {};
|
|
|
|
let { vaId, projectId, logId, logs } = payload;
|
|
|
|
if (!projectId || !logId || !vaId || !Array.isArray(logs)) {
|
|
return res.status(400).json({ success: false, message: "Missing Props" });
|
|
}
|
|
|
|
const vaUser = await VAModel.findById(vaId);
|
|
if (!vaUser) return res.status(400).json({ success: false, message: "VA not found" });
|
|
|
|
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" });
|
|
|
|
if (logs.length === 0) {
|
|
return res.status(400).json({ success: false, message: "No logs to track" });
|
|
}
|
|
|
|
logs[0].from = logEntry.time; // Ensure the first log's 'from' matches the main log time
|
|
|
|
const firstFromLogTime = new Date(logs[0].from);
|
|
const firstToLogTime = new Date(logs[0].to);
|
|
const firstTotalLogTime = Math.abs(firstToLogTime - firstFromLogTime);
|
|
logs[0].totalTime = firstTotalLogTime;
|
|
|
|
|
|
// // 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",
|
|
// });
|
|
// }
|
|
|
|
// Handle POST → create entry
|
|
// if (req.method === "POST") {
|
|
// const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project);
|
|
// const createdTracks = await Promise.all(
|
|
// logs.map(async (log) => {
|
|
// // Calculate total time from timestamps
|
|
// const fromTime = new Date(log.from);
|
|
// const toTime = new Date(log.to);
|
|
// const totalTime = Math.abs(toTime - fromTime);
|
|
// const createdTrack = await WorkTrackModel.create({
|
|
// vaId,
|
|
// projectId,
|
|
// logId,
|
|
// title: log.title,
|
|
// appName: log.appName,
|
|
// url: log.url,
|
|
// from: log.from,
|
|
// to: log.to,
|
|
// totalTime: totalTime,
|
|
// });
|
|
|
|
// return createdTrack;
|
|
// })
|
|
// );
|
|
|
|
const fromLogTime = new Date(logs[0].from);
|
|
const toLogTime = new Date(logs[logs.length -1].to);
|
|
const totalLogTime = Math.abs(toLogTime - fromLogTime);
|
|
|
|
const createdTrack = await WorkTrackModel.create({
|
|
vaId,
|
|
projectId,
|
|
logId,
|
|
from: logs[0].from,
|
|
to: logs[logs.length -1].to,
|
|
totalTime: totalLogTime,
|
|
logs: logs
|
|
});
|
|
|
|
return res.status(201).json({
|
|
success: true,
|
|
message: "Work tracking created",
|
|
data: createdTrack
|
|
});
|
|
// }
|
|
|
|
// // 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 }; |