Compare commits
No commits in common. "320d44f4b58509b1671b0c6cfc3455570ee821e6" and "085e0c0e6dcc1e5efb96717711235225ac156210" have entirely different histories.
320d44f4b5
...
085e0c0e6d
|
|
@ -670,13 +670,6 @@ body {
|
||||||
background-color: rgba(220, 38, 38, 0.1) !important;
|
background-color: rgba(220, 38, 38, 0.1) !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
.additional-info-heading {
|
|
||||||
margin-top: 24px;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
border-top: 1px solid #e5e7eb;
|
|
||||||
padding-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Animations */
|
/* Animations */
|
||||||
@keyframes slideDown {
|
@keyframes slideDown {
|
||||||
from {
|
from {
|
||||||
|
|
|
||||||
|
|
@ -617,11 +617,11 @@
|
||||||
} else {
|
} else {
|
||||||
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
|
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
|
||||||
if (paymentMethod === 'payoneer') {
|
if (paymentMethod === 'payoneer') {
|
||||||
const payoneerAccount = document.getElementById('payoneerAccount').value;
|
// const payoneerAccount = document.getElementById('payoneerAccount').value;
|
||||||
if (!payoneerAccount) {
|
// if (!payoneerAccount) {
|
||||||
showError('Please enter your Payoneer account number', 'payoneerAccount', step);
|
// showError('Please enter your Payoneer account number', 'payoneerAccount', step);
|
||||||
isValid = false;
|
// isValid = false;
|
||||||
}
|
// }
|
||||||
} else if (paymentMethod === 'veem') {
|
} else if (paymentMethod === 'veem') {
|
||||||
const veemAccount = document.getElementById('veemAccount').value;
|
const veemAccount = document.getElementById('veemAccount').value;
|
||||||
if (!veemAccount) {
|
if (!veemAccount) {
|
||||||
|
|
@ -703,9 +703,9 @@
|
||||||
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
|
const paymentMethod = selectedPayment.getAttribute('onclick').match(/'([^']+)'/)[1];
|
||||||
formData.userDetails.paymentMethod = paymentMethod;
|
formData.userDetails.paymentMethod = paymentMethod;
|
||||||
|
|
||||||
if (document.getElementById('payoneerAccount').value) {
|
// if (document.getElementById('payoneerAccount').value) {
|
||||||
formData.userDetails.payoneerAccount = document.getElementById('payoneerAccount').value;
|
// formData.userDetails.payoneerAccount = document.getElementById('payoneerAccount').value;
|
||||||
}
|
// }
|
||||||
if (document.getElementById('veemAccount').value) {
|
if (document.getElementById('veemAccount').value) {
|
||||||
formData.userDetails.veemAccount = document.getElementById('veemAccount').value;
|
formData.userDetails.veemAccount = document.getElementById('veemAccount').value;
|
||||||
}
|
}
|
||||||
|
|
@ -734,7 +734,7 @@
|
||||||
|
|
||||||
// Show appropriate payment details
|
// Show appropriate payment details
|
||||||
if (method === 'payoneer') {
|
if (method === 'payoneer') {
|
||||||
document.getElementById('payoneer-details').style.display = 'block';
|
// document.getElementById('payoneer-details').style.display = 'block';
|
||||||
} else if (method === 'veem') {
|
} else if (method === 'veem') {
|
||||||
document.getElementById('veem-details').style.display = 'block';
|
document.getElementById('veem-details').style.display = 'block';
|
||||||
} else if (method === 'wise') {
|
} else if (method === 'wise') {
|
||||||
|
|
|
||||||
|
|
@ -71,7 +71,7 @@ async function project_add_sf(req, res) {
|
||||||
hourlyRate: hourlyRate || '0',
|
hourlyRate: hourlyRate || '0',
|
||||||
clientId: clientObjectId,
|
clientId: clientObjectId,
|
||||||
vaAssigned: vaAssigned,
|
vaAssigned: vaAssigned,
|
||||||
dedicatedHours: dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"],
|
dedicatedHours: dedicatedHours ? parseFloat(dedicatedHours) : 0,
|
||||||
vaWorkingScheduleStart: vaWorkingScheduleStart || null,
|
vaWorkingScheduleStart: vaWorkingScheduleStart || null,
|
||||||
vaWorkingScheduleEnd: vaWorkingScheduleEnd || null,
|
vaWorkingScheduleEnd: vaWorkingScheduleEnd || null,
|
||||||
isFlexibleAllowed: isFlexibleAllowed || true,
|
isFlexibleAllowed: isFlexibleAllowed || true,
|
||||||
|
|
|
||||||
|
|
@ -74,7 +74,7 @@ async function update_project_sf(req, res) {
|
||||||
|
|
||||||
// Update dedicated hours if provided
|
// Update dedicated hours if provided
|
||||||
if (dedicatedHours !== undefined) {
|
if (dedicatedHours !== undefined) {
|
||||||
updateData.dedicatedHours = dedicatedHours || ["0", "0", "0", "0", "0", "0", "0"];
|
updateData.dedicatedHours = parseFloat(dedicatedHours) || 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update total logged hours if provided
|
// Update total logged hours if provided
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,6 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
|
import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// function to close open work tracks and create logout
|
// function to close open work tracks and create logout
|
||||||
async function closePreviousSession(vaId, lastLog) {
|
async function closePreviousSession(vaId, lastLog) {
|
||||||
|
|
@ -54,7 +53,7 @@ async function closePreviousSession(vaId, lastLog) {
|
||||||
"time": logoutLog.time,
|
"time": logoutLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Apply for leave
|
// Apply for leave
|
||||||
async function va_leave_apply_function(req, res) {
|
async function va_leave_apply_function(req, res) {
|
||||||
|
|
@ -165,7 +164,7 @@ async function va_leave_apply_function(req, res) {
|
||||||
"rejectionReason": ""
|
"rejectionReason": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -26,136 +26,133 @@ function formatHoursToReadable(hours) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// // Helper function to check daily hours and create logout log if needed
|
// Helper function to check daily hours and create logout log if needed
|
||||||
// async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, currentSessionId = null, additionalTimeMs = 0) {
|
async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, currentSessionId = null, additionalTimeMs = 0) {
|
||||||
// try {
|
try {
|
||||||
// const today = new Date();
|
const today = new Date();
|
||||||
// const { start, end } = getDayRange(today);
|
const { start, end } = getDayRange(today);
|
||||||
|
|
||||||
// // Get today's work entries for this VA and project
|
// Get today's work entries for this VA and project
|
||||||
// // Exclude the current session if it's already in the database
|
// Exclude the current session if it's already in the database
|
||||||
// const query = {
|
const query = {
|
||||||
// vaId,
|
vaId,
|
||||||
// projectId,
|
projectId,
|
||||||
// from: { $gte: start, $lte: end }
|
from: { $gte: start, $lte: end }
|
||||||
// };
|
};
|
||||||
|
|
||||||
// if (currentSessionId) {
|
if (currentSessionId) {
|
||||||
// query._id = { $ne: currentSessionId };
|
query._id = { $ne: currentSessionId };
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const todayWorkEntries = await WorkTrackModel.find(query);
|
const todayWorkEntries = await WorkTrackModel.find(query);
|
||||||
|
|
||||||
// // Calculate total hours worked today
|
// Calculate total hours worked today
|
||||||
// let totalTodayMs = todayWorkEntries.reduce((total, entry) => {
|
let totalTodayMs = todayWorkEntries.reduce((total, entry) => {
|
||||||
// return total + (entry.totalTime || 0);
|
return total + (entry.totalTime || 0);
|
||||||
// }, 0);
|
}, 0);
|
||||||
|
|
||||||
// // Add the additional time from current session if provided
|
// Add the additional time from current session if provided
|
||||||
// totalTodayMs += additionalTimeMs;
|
totalTodayMs += additionalTimeMs;
|
||||||
|
|
||||||
// const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
|
const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
|
||||||
|
|
||||||
// // Check if daily dedicated hours are completed
|
// Check if daily dedicated hours are completed
|
||||||
// if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
|
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) {
|
||||||
// // Create automatic logout log
|
// Create automatic logout log
|
||||||
// const logoutLog = await LoggedModel.create({
|
const logoutLog = await LoggedModel.create({
|
||||||
// loggedType: "LOGOUT",
|
loggedType: "LOGOUT",
|
||||||
// vaId,
|
vaId,
|
||||||
// vaEmail,
|
vaEmail,
|
||||||
// projectId
|
projectId
|
||||||
// });
|
});
|
||||||
|
|
||||||
// try {
|
try {
|
||||||
// const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`;
|
||||||
// const sfDataSync = {
|
const sfDataSync = {
|
||||||
// "_id": logoutLog._id,
|
"_id": logoutLog._id,
|
||||||
// "loggedType": "LOGOUT",
|
"loggedType": "LOGOUT",
|
||||||
// "vaId": vaId,
|
"vaId": vaId,
|
||||||
// "vaEmail": vaEmail,
|
"vaEmail": vaEmail,
|
||||||
// "projectId": projectId,
|
"projectId": projectId,
|
||||||
// "time": logoutLog.time,
|
"time": logoutLog.time,
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const response = await fetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
// method: 'POST',
|
method: 'POST',
|
||||||
// headers: {
|
headers: {
|
||||||
// 'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
// // Add any required headers for Salesforce authentication
|
// Add any required headers for Salesforce authentication
|
||||||
// // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
|
// 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN
|
||||||
// },
|
},
|
||||||
// body: JSON.stringify(sfDataSync)
|
body: JSON.stringify(sfDataSync)
|
||||||
// });
|
});
|
||||||
|
|
||||||
// if (!response.ok) {
|
if (!response.ok) {
|
||||||
// const errorText = await response.text();
|
const errorText = await response.text();
|
||||||
// throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
|
throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const result = await response.json();
|
const result = await response.json();
|
||||||
// // console.log("✅ Successfully synced VA data to Salesforce:", result);
|
// console.log("✅ Successfully synced VA data to Salesforce:", result);
|
||||||
|
|
||||||
// return {
|
return {
|
||||||
// shouldLogout: true,
|
shouldLogout: true,
|
||||||
// totalTodayHours: totalTodayHours,
|
totalTodayHours: totalTodayHours,
|
||||||
// dedicatedHours: project.dedicatedHours,
|
dedicatedHours: project.dedicatedHours,
|
||||||
// logoutLogId: logoutLog._id,
|
logoutLogId: logoutLog._id,
|
||||||
// SyncSalesforce: result
|
SyncSalesforce: result
|
||||||
// };
|
};
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// console.error("Failed to sync with Salesforce:", error.message);
|
console.error("Failed to sync with Salesforce:", error.message);
|
||||||
// console.log(error)
|
console.log(error)
|
||||||
|
|
||||||
// // Create a custom error to distinguish Salesforce errors
|
// Create a custom error to distinguish Salesforce errors
|
||||||
// const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||||
// salesforceError.isSalesforceError = true;
|
salesforceError.isSalesforceError = true;
|
||||||
// throw salesforceError;
|
throw salesforceError;
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return {
|
return {
|
||||||
// shouldLogout: false,
|
shouldLogout: false,
|
||||||
// totalTodayHours: totalTodayHours,
|
totalTodayHours: totalTodayHours,
|
||||||
// dedicatedHours: project.dedicatedHours
|
dedicatedHours: project.dedicatedHours
|
||||||
// };
|
};
|
||||||
// } catch (error) {
|
} catch (error) {
|
||||||
// console.error("Error in checkDailyHoursAndLogout:", error);
|
console.error("Error in checkDailyHoursAndLogout:", error);
|
||||||
// return { shouldLogout: false, error: error.message };
|
return { shouldLogout: false, error: error.message };
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
// Getting logged hours in a specific project of VA
|
// Getting logged hours in a specific project of VA
|
||||||
async function va_log_track_work_callback(req, res) {
|
async function va_log_track_work_callback(req, res) {
|
||||||
try {
|
try {
|
||||||
// const accessToken = req.headers.token;
|
const accessToken = req.headers.token;
|
||||||
// if (!accessToken) {
|
if (!accessToken) {
|
||||||
// return res.status(401).json({ success: false, message: "Missing token" });
|
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);
|
const decoded = jwt.verify(accessToken, process.env.JWT_SECRET_ACCESS_TOKEN);
|
||||||
if (!vaUser) return res.status(400).json({ success: false, message: "VA not found" });
|
|
||||||
|
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 { projectId, logId, title, appName, url = null } = payload;
|
||||||
|
|
||||||
|
if (title === "") {
|
||||||
|
title = ""
|
||||||
|
}
|
||||||
|
|
||||||
const project = await ProjectModel.findById(projectId);
|
const project = await ProjectModel.findById(projectId);
|
||||||
if (!project) return res.status(400).json({ success: false, message: "Project not found" });
|
if (!project) return res.status(400).json({ success: false, message: "Project not found" });
|
||||||
|
|
@ -163,137 +160,117 @@ async function va_log_track_work_callback(req, res) {
|
||||||
const logEntry = await LoggedModel.findById(logId);
|
const logEntry = await LoggedModel.findById(logId);
|
||||||
if (!logEntry) return res.status(400).json({ success: false, message: "Log entry not found" });
|
if (!logEntry) return res.status(400).json({ success: false, message: "Log entry not found" });
|
||||||
|
|
||||||
if (logs.length === 0) {
|
// Check last log entry for this VA and project
|
||||||
return res.status(400).json({ success: false, message: "No logs to track" });
|
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",
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
logs[0].from = logEntry.time; // Ensure the first log's 'from' matches the main log time
|
if (!projectId || !logId || !title || !appName) {
|
||||||
|
return res.status(400).json({ success: false, message: "Missing Props" });
|
||||||
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
|
// Handle POST → create entry
|
||||||
// if (req.method === "POST") {
|
if (req.method === "POST") {
|
||||||
// const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project);
|
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);
|
if (dailyCheck.shouldLogout) {
|
||||||
const toLogTime = new Date(logs[logs.length -1].to);
|
return res.status(400).json({
|
||||||
const totalLogTime = Math.abs(toLogTime - fromLogTime);
|
success: false,
|
||||||
|
message: `Cannot start work - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`,
|
||||||
|
autoLogout: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const createdTrack = await WorkTrackModel.create({
|
const createdTrack = await WorkTrackModel.create({
|
||||||
vaId,
|
vaId,
|
||||||
projectId,
|
projectId,
|
||||||
logId,
|
logId,
|
||||||
from: logs[0].from,
|
title,
|
||||||
to: logs[logs.length -1].to,
|
appName,
|
||||||
totalTime: totalLogTime,
|
url,
|
||||||
logs: logs
|
from: Date.now(),
|
||||||
});
|
});
|
||||||
|
|
||||||
return res.status(201).json({
|
return res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Work tracking created",
|
message: "Work tracking started",
|
||||||
data: createdTrack
|
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)
|
// Handle PATCH → update entry (add 'to' and calculate total time)
|
||||||
// if (req.method === "PATCH") {
|
if (req.method === "PATCH") {
|
||||||
// const { id } = payload; // expecting the workTrack record ID to update
|
const { id } = payload; // expecting the workTrack record ID to update
|
||||||
// if (!id) {
|
if (!id) {
|
||||||
// return res.status(400).json({ success: false, message: "Missing workTrack ID" });
|
return res.status(400).json({ success: false, message: "Missing workTrack ID" });
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const workEntry = await WorkTrackModel.findById(id);
|
const workEntry = await WorkTrackModel.findById(id);
|
||||||
// if (!workEntry) {
|
if (!workEntry) {
|
||||||
// return res.status(404).json({ success: false, message: "Work entry not found" });
|
return res.status(404).json({ success: false, message: "Work entry not found" });
|
||||||
// }
|
}
|
||||||
|
|
||||||
// const toTime = new Date();
|
const toTime = new Date();
|
||||||
// const fromTime = new Date(workEntry.from);
|
const fromTime = new Date(workEntry.from);
|
||||||
// const totalTimeMs = toTime - fromTime;
|
const totalTimeMs = toTime - fromTime;
|
||||||
|
|
||||||
// // Optional: store totalTime in minutes or hours instead of raw ms
|
// Optional: store totalTime in minutes or hours instead of raw ms
|
||||||
// const hours = Math.floor(totalTimeMs / (1000 * 60 * 60));
|
const hours = Math.floor(totalTimeMs / (1000 * 60 * 60));
|
||||||
// const minutes = Math.floor((totalTimeMs % (1000 * 60 * 60)) / (1000 * 60));
|
const minutes = Math.floor((totalTimeMs % (1000 * 60 * 60)) / (1000 * 60));
|
||||||
// const seconds = Math.floor((totalTimeMs % (1000 * 60)) / 1000);
|
const seconds = Math.floor((totalTimeMs % (1000 * 60)) / 1000);
|
||||||
|
|
||||||
// const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`;
|
const formattedTotalTime = `${hours}h ${minutes}m ${seconds}s`;
|
||||||
|
|
||||||
// // Check if daily dedicated hours are completed after this work session
|
// Check if daily dedicated hours are completed after this work session
|
||||||
// const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, id, totalTimeMs);
|
const dailyCheck = await checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, id, totalTimeMs);
|
||||||
|
|
||||||
// workEntry.to = toTime;
|
workEntry.to = toTime;
|
||||||
// workEntry.totalTime = totalTimeMs; // store as number of minutes
|
workEntry.totalTime = totalTimeMs; // store as number of minutes
|
||||||
// await workEntry.save();
|
await workEntry.save();
|
||||||
|
|
||||||
// const response = {
|
const response = {
|
||||||
// success: true,
|
success: true,
|
||||||
// message: "Work tracking stopped",
|
message: "Work tracking stopped",
|
||||||
// data: {
|
data: {
|
||||||
// id: workEntry._id,
|
id: workEntry._id,
|
||||||
// from: workEntry.from,
|
from: workEntry.from,
|
||||||
// to: workEntry.to,
|
to: workEntry.to,
|
||||||
// formattedTotalTime,
|
formattedTotalTime,
|
||||||
// totalTimeMs,
|
totalTimeMs,
|
||||||
// sessionHours: totalTimeMs / (1000 * 60 * 60)
|
sessionHours: totalTimeMs / (1000 * 60 * 60)
|
||||||
// },
|
},
|
||||||
// dailyHours: {
|
dailyHours: {
|
||||||
// todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
|
todayTotal: formatHoursToReadable(dailyCheck.totalTodayHours),
|
||||||
// dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
|
dailyLimit: formatHoursToReadable(dailyCheck.dedicatedHours),
|
||||||
// remaining: formatHoursToReadable(Math.max(0, dailyCheck.dedicatedHours - dailyCheck.totalTodayHours))
|
remaining: formatHoursToReadable(Math.max(0, dailyCheck.dedicatedHours - dailyCheck.totalTodayHours))
|
||||||
// }
|
}
|
||||||
// };
|
};
|
||||||
|
|
||||||
// // If auto-logout was triggered, add that info to response
|
// If auto-logout was triggered, add that info to response
|
||||||
// if (dailyCheck.shouldLogout) {
|
if (dailyCheck.shouldLogout) {
|
||||||
// response.autoLogout = true;
|
response.autoLogout = true;
|
||||||
// response.message = `Work tracking stopped. Auto-logout created - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`;
|
response.message = `Work tracking stopped. Auto-logout created - daily hours completed (${formatHoursToReadable(dailyCheck.totalTodayHours)} / ${formatHoursToReadable(dailyCheck.dedicatedHours)})`;
|
||||||
// response.logoutLogId = dailyCheck.logoutLogId;
|
response.logoutLogId = dailyCheck.logoutLogId;
|
||||||
// }
|
}
|
||||||
|
|
||||||
// return res.status(200).json(response);
|
return res.status(200).json(response);
|
||||||
// }
|
}
|
||||||
|
|
||||||
// Fallback for unsupported methods
|
// Fallback for unsupported methods
|
||||||
// return res.status(405).json({ success: false, message: "Method not allowed" });
|
return res.status(405).json({ success: false, message: "Method not allowed" });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
return res.status(500).send("Internal Server Error");
|
return res.status(500).send("Internal Server Error");
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { VAModel } from "../../../models/vaModel.js";
|
import { VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
async function va_payslip_data_function(req, res) {
|
async function va_payslip_data_function(req, res) {
|
||||||
try {
|
try {
|
||||||
|
|
@ -12,7 +11,7 @@ async function va_payslip_data_function(req, res) {
|
||||||
|
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-payslip/${vaId}`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-payslip/${vaId}`;
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ async function va_payroll_schedule_function(req, res) {
|
||||||
try {
|
try {
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/payroll-schedule`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/payroll-schedule`;
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import bcrypt from "bcrypt";
|
import bcrypt from "bcrypt";
|
||||||
import { VAModel } from "../../../models/vaModel.js";
|
import { VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Register a new VA
|
// Register a new VA
|
||||||
async function va_register(req, res) {
|
async function va_register(req, res) {
|
||||||
|
|
@ -54,7 +53,7 @@ async function va_register(req, res) {
|
||||||
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/virtualassistant`;
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/virtualassistant`;
|
||||||
delete created.userDetails._id;
|
delete created.userDetails._id;
|
||||||
created.userDetails._id = created._id; // Include VA's MongoDB ID
|
created.userDetails._id = created._id; // Include VA's MongoDB ID
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
|
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Helper function to get start and end of day in UTC
|
// Helper function to get start and end of day in UTC
|
||||||
function getDayRange(date = new Date()) {
|
function getDayRange(date = new Date()) {
|
||||||
|
|
@ -198,7 +197,7 @@ async function closePreviousSession(vaId, lastLog, logTime = new Date()) {
|
||||||
"time": logoutLog.time,
|
"time": logoutLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -290,129 +289,6 @@ async function va_project_log_callback(req, res) {
|
||||||
// });
|
// });
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// If Logout then Update work track entries
|
|
||||||
if (type === "LOGOUT") {
|
|
||||||
const { logId } = payload;
|
|
||||||
|
|
||||||
const workTrackData = await WorkTrackModel.find({
|
|
||||||
logId: logId
|
|
||||||
});
|
|
||||||
|
|
||||||
// console.log(`Found work track data for logout:`, workTrackData);
|
|
||||||
|
|
||||||
if (workTrackData[0]) {
|
|
||||||
const fromLogTime = new Date(workTrackData[0].from);
|
|
||||||
const toLogTime = new Date(logTime);
|
|
||||||
const totalTimeMs = toLogTime - fromLogTime;
|
|
||||||
|
|
||||||
const updatedLogs = workTrackData[0].logs ? [...workTrackData[0].logs] : [];
|
|
||||||
|
|
||||||
if (updatedLogs.length > 0) {
|
|
||||||
const lastLogIndex = updatedLogs.length - 1;
|
|
||||||
const lastLogEntry = updatedLogs[lastLogIndex];
|
|
||||||
|
|
||||||
// Update the last log's 'to' time and recalculate its totalTime
|
|
||||||
const lastLogFromTime = new Date(lastLogEntry.from);
|
|
||||||
const lastLogTotalTime = Math.abs(toLogTime - lastLogFromTime);
|
|
||||||
|
|
||||||
updatedLogs[lastLogIndex] = {
|
|
||||||
...lastLogEntry,
|
|
||||||
to: toLogTime,
|
|
||||||
totalTime: lastLogTotalTime
|
|
||||||
};
|
|
||||||
|
|
||||||
// console.log(`Updated last log in array - from: ${lastLogFromTime.toISOString()}, to: ${toLogTime.toISOString()}, totalTime: ${lastLogTotalTime}ms`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateResult = await WorkTrackModel.updateOne(
|
|
||||||
{ _id: workTrackData[0]._id },
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
to: toLogTime,
|
|
||||||
totalTime: totalTimeMs,
|
|
||||||
logs: updatedLogs
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(`Updated work track ${workTrackData[0]._id}:`, {
|
|
||||||
from: fromLogTime.toISOString(),
|
|
||||||
to: toLogTime.toISOString(),
|
|
||||||
totalTime: totalTimeMs,
|
|
||||||
logsUpdated: updatedLogs.length,
|
|
||||||
mongodbUpdateResult: updateResult
|
|
||||||
});
|
|
||||||
|
|
||||||
// const updatedWorkTrack = await WorkTrackModel.findById(workTrackData[0]._id);
|
|
||||||
// console.log("Verified updated work track:", {
|
|
||||||
// to: updatedWorkTrack?.to,
|
|
||||||
// totalTime: updatedWorkTrack?.totalTime,
|
|
||||||
// logsCount: updatedWorkTrack?.logs?.length,
|
|
||||||
// lastLog: updatedWorkTrack?.logs?.[updatedWorkTrack.logs.length - 1]
|
|
||||||
// });
|
|
||||||
} else {
|
|
||||||
console.log(`No work track found for vaId: ${vaId}, projectId: ${projectId}, logId: ${logId}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Create the logout log entry
|
|
||||||
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 authFetch(salesforceUrl, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
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();
|
|
||||||
|
|
||||||
return res.status(201).json({
|
|
||||||
success: true,
|
|
||||||
message: `${type} Successfully`,
|
|
||||||
logId: createdLog._id,
|
|
||||||
SyncSalesforce: result,
|
|
||||||
timeUsed: receivedTimeString ? 'received' : 'current',
|
|
||||||
actualTime: createdLog.time,
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error("Failed to sync with Salesforce:", error.message);
|
|
||||||
console.log(error)
|
|
||||||
|
|
||||||
// Still return success since the logout was recorded locally
|
|
||||||
return res.status(201).json({
|
|
||||||
success: true,
|
|
||||||
message: `${type} Successfully (Salesforce sync failed)`,
|
|
||||||
logId: createdLog._id,
|
|
||||||
timeUsed: receivedTimeString ? 'received' : 'current',
|
|
||||||
actualTime: createdLog.time,
|
|
||||||
warning: "Salesforce sync failed"
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// For LOGIN requests, check daily hours (using UTC day range)
|
// For LOGIN requests, check daily hours (using UTC day range)
|
||||||
if (type === "LOGIN") {
|
if (type === "LOGIN") {
|
||||||
const { start, end } = getDayRange(); // UTC day range
|
const { start, end } = getDayRange(); // UTC day range
|
||||||
|
|
@ -472,7 +348,7 @@ async function va_project_log_callback(req, res) {
|
||||||
"time": createdLog.time,
|
"time": createdLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -534,7 +410,7 @@ async function va_project_log_callback(req, res) {
|
||||||
"time": createdLog.time,
|
"time": createdLog.time,
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -63,18 +63,6 @@ async function va_projects_report_function(req, res) {
|
||||||
if (!logs.length) {
|
if (!logs.length) {
|
||||||
return res.json({ success: true, message: "No logs found for this project", data: [] });
|
return res.json({ success: true, message: "No logs found for this project", data: [] });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get all work tracks for this VA and project in the date range
|
|
||||||
const workTracks = await WorkTrackModel.find({
|
|
||||||
projectId,
|
|
||||||
vaId,
|
|
||||||
$or: [
|
|
||||||
{ from: { $gte: fromDate, $lte: toDate } },
|
|
||||||
{ to: { $gte: fromDate, $lte: toDate } }
|
|
||||||
]
|
|
||||||
})
|
|
||||||
.sort({ from: 1 })
|
|
||||||
.lean();
|
|
||||||
|
|
||||||
// Unique VA IDs
|
// Unique VA IDs
|
||||||
const vaIds = [...new Set(logs.map(log => log.vaId?.toString()))].filter(Boolean);
|
const vaIds = [...new Set(logs.map(log => log.vaId?.toString()))].filter(Boolean);
|
||||||
|
|
@ -85,120 +73,55 @@ async function va_projects_report_function(req, res) {
|
||||||
|
|
||||||
const projectReport = [];
|
const projectReport = [];
|
||||||
|
|
||||||
for (const currentVaId of vaIds) {
|
for (const vaId of vaIds) {
|
||||||
const vaLogList = logs.filter(l => l.vaId.toString() === currentVaId);
|
const vaLogList = logs.filter(l => l.vaId.toString() === vaId);
|
||||||
let sessions = [];
|
let sessions = [];
|
||||||
let currentLogin = null;
|
let currentLogin = null;
|
||||||
const vaWorkTracks = workTracks.filter(w => w.vaId.toString() === currentVaId);
|
|
||||||
|
|
||||||
// Pair login → logout
|
// Pair login → logout
|
||||||
for (const entry of vaLogList) {
|
for (const entry of vaLogList) {
|
||||||
if (entry.loggedType === "LOGIN") {
|
if (entry.loggedType === "LOGIN") {
|
||||||
currentLogin = entry;
|
currentLogin = entry;
|
||||||
} else if (entry.loggedType === "LOGOUT" && currentLogin) {
|
} else if (entry.loggedType === "LOGOUT" && currentLogin) {
|
||||||
sessions.push({ from: currentLogin.time, to: entry.time, logId: currentLogin._id });
|
sessions.push({ from: currentLogin.time, to: entry.time });
|
||||||
currentLogin = null;
|
currentLogin = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If still logged in, close at end of day
|
// If still logged in, close at end of day
|
||||||
if (currentLogin) {
|
if (currentLogin) {
|
||||||
sessions.push({ from: currentLogin.time, to: toDate, logId: currentLogin._id });
|
sessions.push({ from: currentLogin.time, to: toDate });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaDetails = vaUsers.find(v => v._id.toString() === currentVaId);
|
const vaDetails = vaUsers.find(v => v._id.toString() === vaId);
|
||||||
let totalWorkMs = 0;
|
let totalWorkMs = 0;
|
||||||
const sessionDetails = [];
|
const sessionDetails = [];
|
||||||
|
|
||||||
// for (const s of sessions) {
|
for (const s of sessions) {
|
||||||
// const workData = await WorkTrackModel.find({
|
const workData = await WorkTrackModel.find({
|
||||||
// vaId,
|
vaId,
|
||||||
// projectId,
|
projectId,
|
||||||
// from: { $gte: s.from },
|
from: { $gte: s.from },
|
||||||
// to: { $lte: s.to }
|
to: { $lte: s.to }
|
||||||
// }).sort({ from: 1 }).lean();
|
}).sort({ from: 1 }).lean();
|
||||||
|
|
||||||
// const tabs = workData.map(t => ({
|
const tabs = workData.map(t => ({
|
||||||
// title: t.title,
|
title: t.title,
|
||||||
// appName: t.appName,
|
appName: t.appName,
|
||||||
// from: t.from,
|
from: t.from,
|
||||||
// to: t.to,
|
to: t.to,
|
||||||
// durationMs: t.totalTime,
|
durationMs: t.totalTime,
|
||||||
// durationFormatted: formatHMS(t.totalTime),
|
durationFormatted: formatHMS(t.totalTime),
|
||||||
// }));
|
}));
|
||||||
|
|
||||||
// const sessionWorkMs = workData.reduce((sum, w) => sum + (w.totalTime || 0), 0);
|
const sessionWorkMs = workData.reduce((sum, w) => sum + (w.totalTime || 0), 0);
|
||||||
// totalWorkMs += sessionWorkMs;
|
totalWorkMs += sessionWorkMs;
|
||||||
|
|
||||||
// sessionDetails.push({
|
|
||||||
// from: s.from,
|
|
||||||
// to: s.to,
|
|
||||||
// totalSessionTime: formatHMS(sessionWorkMs),
|
|
||||||
// tabs,
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
|
|
||||||
for (const session of sessions) {
|
|
||||||
// Find work tracks for this specific session (matching logId)
|
|
||||||
const sessionWorkTracks = vaWorkTracks.filter(
|
|
||||||
track => track.logId && track.logId.toString() === session.logId.toString()
|
|
||||||
);
|
|
||||||
|
|
||||||
let sessionTabs = [];
|
|
||||||
let sessionTotalMs = 0;
|
|
||||||
|
|
||||||
// Extract all logs from work tracks for this session
|
|
||||||
for (const workTrack of sessionWorkTracks) {
|
|
||||||
if (workTrack.logs && Array.isArray(workTrack.logs)) {
|
|
||||||
// Process each log entry in the logs array
|
|
||||||
for (const logEntry of workTrack.logs) {
|
|
||||||
// Calculate duration for this log entry
|
|
||||||
const logFrom = new Date(logEntry.from);
|
|
||||||
const logTo = new Date(logEntry.to || workTrack.to || session.to);
|
|
||||||
const logDuration = logEntry.totalTime || (logTo - logFrom);
|
|
||||||
|
|
||||||
sessionTotalMs += logDuration;
|
|
||||||
|
|
||||||
sessionTabs.push({
|
|
||||||
title: logEntry.title || "",
|
|
||||||
appName: logEntry.appName || "",
|
|
||||||
url: logEntry.url || null,
|
|
||||||
from: logFrom,
|
|
||||||
to: logTo,
|
|
||||||
durationMs: logDuration,
|
|
||||||
durationFormatted: formatHMS(logDuration),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Also add the main work track if it has separate data
|
|
||||||
if (workTrack.totalTime && workTrack.totalTime > 0) {
|
|
||||||
sessionTotalMs += workTrack.totalTime;
|
|
||||||
|
|
||||||
// Only add if we don't have logs array data
|
|
||||||
if (!workTrack.logs || workTrack.logs.length === 0) {
|
|
||||||
sessionTabs.push({
|
|
||||||
title: workTrack.title || "",
|
|
||||||
appName: workTrack.appName || "",
|
|
||||||
url: workTrack.url || null,
|
|
||||||
from: workTrack.from,
|
|
||||||
to: workTrack.to,
|
|
||||||
durationMs: workTrack.totalTime,
|
|
||||||
durationFormatted: formatHMS(workTrack.totalTime),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
totalWorkMs += sessionTotalMs;
|
|
||||||
|
|
||||||
sessionDetails.push({
|
sessionDetails.push({
|
||||||
from: session.from,
|
from: s.from,
|
||||||
to: session.to,
|
to: s.to,
|
||||||
totalSessionTime: formatHMS(sessionTotalMs),
|
totalSessionTime: formatHMS(sessionWorkMs),
|
||||||
totalSessionMs: sessionTotalMs,
|
tabs,
|
||||||
workTracksCount: sessionWorkTracks.length,
|
|
||||||
tabs: sessionTabs.sort((a, b) => a.from - b.from), // Sort tabs by time
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,5 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
|
import { VAModel, ProjectModel, AdjustedHoursModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Adjust missing hours by creating new records
|
// Adjust missing hours by creating new records
|
||||||
async function va_timesheet_adjustment_function(req, res) {
|
async function va_timesheet_adjustment_function(req, res) {
|
||||||
|
|
@ -156,7 +155,7 @@ async function va_timesheet_adjustment_function(req, res) {
|
||||||
"adjustedHours": formatTime(adjustedSeconds)
|
"adjustedHours": formatTime(adjustedSeconds)
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
import { VAModel, TipsModel, ClientModel } from "../../../models/vaModel.js";
|
import { VAModel, TipsModel, ClientModel } from "../../../models/vaModel.js";
|
||||||
import authFetch from "../../../utils/authFetch.js";
|
|
||||||
|
|
||||||
// Approve or reject leave
|
// Approve or reject leave
|
||||||
async function va_tips_manage_function(req, res) {
|
async function va_tips_manage_function(req, res) {
|
||||||
|
|
@ -39,7 +38,7 @@ async function va_tips_manage_function(req, res) {
|
||||||
"tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
"tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
||||||
}
|
}
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
@ -117,7 +116,7 @@ async function va_tips_delete(req, res) {
|
||||||
// "tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
// "tipAmount": Number(parseFloat(createdTip.amount).toFixed(2)), // Convert to cents
|
||||||
// }
|
// }
|
||||||
|
|
||||||
const response = await authFetch(salesforceUrl, {
|
const response = await fetch(salesforceUrl, {
|
||||||
method: 'DELETE',
|
method: 'DELETE',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
|
|
|
||||||
|
|
@ -101,8 +101,8 @@ const projectSchema = new Schema({
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
dedicatedHours: {
|
dedicatedHours: {
|
||||||
type: Array,
|
type: String,
|
||||||
default: ["0", "0", "0", "0", "0", "0", "0"]
|
default: "0"
|
||||||
},
|
},
|
||||||
status: {
|
status: {
|
||||||
type: String,
|
type: String,
|
||||||
|
|
@ -344,6 +344,15 @@ const vaWorkTrackSchema = new Schema({
|
||||||
type: Types.ObjectId,
|
type: Types.ObjectId,
|
||||||
ref: "TrackLog",
|
ref: "TrackLog",
|
||||||
},
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
appName: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
url: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
from: {
|
from: {
|
||||||
type: Date,
|
type: Date,
|
||||||
default: Date.now,
|
default: Date.now,
|
||||||
|
|
@ -356,10 +365,6 @@ const vaWorkTrackSchema = new Schema({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
logs: {
|
|
||||||
type: Array,
|
|
||||||
default: [],
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,7 @@ import { va_remove_function } from "../../controllers/va/vaProfile/vaDelete.cont
|
||||||
import { va_payroll_schedule_function } from "../../controllers/va/vaPayroll/vaPayrollSchedule.controller.js";
|
import { va_payroll_schedule_function } from "../../controllers/va/vaPayroll/vaPayrollSchedule.controller.js";
|
||||||
import { va_payslip_data_function } from "../../controllers/va/vaPayroll/vaPaySlip.controller.js";
|
import { va_payslip_data_function } from "../../controllers/va/vaPayroll/vaPaySlip.controller.js";
|
||||||
|
|
||||||
vaRoutes.get("/", va_callback); // VA Callback for getting active VA list
|
vaRoutes.get("/", va_callback); // VA Callback for getting active VA
|
||||||
|
|
||||||
vaRoutes.post("/login", va_login_callback); // VA Callback for login VA
|
vaRoutes.post("/login", va_login_callback); // VA Callback for login VA
|
||||||
vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for login VA
|
vaRoutes.get("/logout", authValidator, va_logout_callback); // VA Callback for login VA
|
||||||
|
|
@ -76,7 +76,7 @@ vaRoutes.get("/list/projects/:projectId?/:from?/:to?", authValidator, va_project
|
||||||
vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged IN / Logged Out in project
|
vaRoutes.post("/log/project", authValidator, va_project_log_callback); // VA Logged IN / Logged Out in project
|
||||||
|
|
||||||
vaRoutes.post("/log/project/track", authValidator, va_log_track_work_callback); // Track Window Tabs Logs Data
|
vaRoutes.post("/log/project/track", authValidator, va_log_track_work_callback); // Track Window Tabs Logs Data
|
||||||
// vaRoutes.patch("/log/project/track", authValidator, va_log_track_work_callback); // Update Track Window Tab Closing Time
|
vaRoutes.patch("/log/project/track", authValidator, va_log_track_work_callback); // Update Track Window Tab Closing Time
|
||||||
vaRoutes.get("/log/workReport/", authValidator, va_log_work_report_callback); // VA Audit Report // All Projects
|
vaRoutes.get("/log/workReport/", authValidator, va_log_work_report_callback); // VA Audit Report // All Projects
|
||||||
vaRoutes.get("/log/project/:projectId/:from?/:to?", authValidator, va_log_callback); // VA / Project Audit Report
|
vaRoutes.get("/log/project/:projectId/:from?/:to?", authValidator, va_log_callback); // VA / Project Audit Report
|
||||||
// VA filter search: supports query params `name`, `email`, `from`, `to`
|
// VA filter search: supports query params `name`, `email`, `from`, `to`
|
||||||
|
|
|
||||||
|
|
@ -1,49 +0,0 @@
|
||||||
import fetch from "node-fetch"; // or global fetch in Node 18+
|
|
||||||
|
|
||||||
let options = {
|
|
||||||
token: ''
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
const getAccessToken = async () => {
|
|
||||||
try {
|
|
||||||
|
|
||||||
|
|
||||||
const response = await fetch(`${process.env.SF_DOCTRACKER_API_URL}services/oauth2/token`, {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/x-www-form-urlencoded',
|
|
||||||
},
|
|
||||||
body: new URLSearchParams({
|
|
||||||
grant_type: 'client_credentials',
|
|
||||||
client_id: process.env.SF_CLIENT_ID,
|
|
||||||
client_secret: process.env.SF_CLIENT_SECRET,
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
console.log(response)
|
|
||||||
options.token = (await response.json()).access_token;
|
|
||||||
return options.token;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Error fetching access token:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
||||||
|
|
||||||
export const authFetch = async (url, options = {}) => {
|
|
||||||
const token = await getAccessToken();
|
|
||||||
|
|
||||||
return fetch(url, {
|
|
||||||
...options,
|
|
||||||
headers: {
|
|
||||||
...(options.headers || {}),
|
|
||||||
"Content-Type": "application/json",
|
|
||||||
Authorization: `Bearer ${token}`,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
export default authFetch;
|
|
||||||
export { getAccessToken }
|
|
||||||
|
|
@ -261,7 +261,23 @@
|
||||||
<div class="error-message" id="step-4-error-message"></div>
|
<div class="error-message" id="step-4-error-message"></div>
|
||||||
</div>
|
</div>
|
||||||
<form id="payment-form">
|
<form id="payment-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
<!-- <div class="payment-option" onclick="selectPaymentMethod('payoneer')">
|
||||||
|
<h4>Payoneer</h4>
|
||||||
|
<p>Receive payments via Payoneer</p>
|
||||||
|
</div>
|
||||||
|
<div id="payoneer-details" class="payment-details">
|
||||||
|
<div class="form-group">
|
||||||
|
<div class="input-container">
|
||||||
|
<input type="text" id="payoneerAccount" class="input-field" placeholder=" " minlength="5" maxlength="20" oninput="validatePayoneerAccount(this)" onkeypress="return event.charCode >= 48 && event.charCode <= 57" required>
|
||||||
|
<label for="payoneerAccount" class="input-label">Payoneer Account Number *</label>
|
||||||
|
</div>
|
||||||
|
<div class="invalid-feedback" id="payoneer-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
||||||
|
Account number must be 5-20 characters
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div> -->
|
||||||
|
|
||||||
<div class="payment-option" onclick="selectPaymentMethod('veem')">
|
<div class="payment-option" onclick="selectPaymentMethod('veem')">
|
||||||
<h4>Veem</h4>
|
<h4>Veem</h4>
|
||||||
<p>Receive payments via Veem</p>
|
<p>Receive payments via Veem</p>
|
||||||
|
|
@ -293,23 +309,6 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<h2 class="form-title additional-info-heading">Additional Information</h2>
|
|
||||||
<div class="payment-option" onclick="selectPaymentMethod('payoneer')">
|
|
||||||
<h4>Payoneer</h4>
|
|
||||||
<p>Receive payments via Payoneer</p>
|
|
||||||
</div>
|
|
||||||
<div id="payoneer-details" class="payment-details">
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="input-container">
|
|
||||||
<input type="text" id="payoneerAccount" class="input-field" placeholder=" " minlength="5" maxlength="20" oninput="validatePayoneerAccount(this)" onkeypress="return event.charCode >= 48 && event.charCode <= 57" required>
|
|
||||||
<label for="payoneerAccount" class="input-label">Payoneer Account Number *</label>
|
|
||||||
</div>
|
|
||||||
<div class="invalid-feedback" id="payoneer-error" style="display: none; color: #dc3545; font-size: 12px; margin-top: 5px;">
|
|
||||||
Account number must be 5-20 characters
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-actions">
|
<div class="form-actions">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue