Updated track work API
This commit is contained in:
parent
645adea832
commit
287d078b49
|
|
@ -167,44 +167,70 @@ async function va_log_track_work_callback(req, res) {
|
||||||
return res.status(400).json({ success: false, message: "No logs to track" });
|
return res.status(400).json({ success: false, message: "No logs to track" });
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check last log entry for this VA and project
|
logs[0].from = logEntry.time; // Ensure the first log's 'from' matches the main log time
|
||||||
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
|
const firstFromLogTime = new Date(logs[0].from);
|
||||||
if (lastLog.loggedType === "LOGOUT") {
|
const firstToLogTime = new Date(logs[0].to);
|
||||||
return res.status(400).json({
|
const firstTotalLogTime = Math.abs(firstToLogTime - firstFromLogTime);
|
||||||
success: false,
|
logs[0].totalTime = firstTotalLogTime;
|
||||||
message: "User has already logged out from this project",
|
|
||||||
});
|
|
||||||
}
|
// // 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(
|
// const createdTracks = await Promise.all(
|
||||||
logs.map(async (log) => {
|
// logs.map(async (log) => {
|
||||||
const createdTrack = await WorkTrackModel.create({
|
// // Calculate total time from timestamps
|
||||||
vaId,
|
// const fromTime = new Date(log.from);
|
||||||
projectId,
|
// const toTime = new Date(log.to);
|
||||||
logId,
|
// const totalTime = Math.abs(toTime - fromTime);
|
||||||
titlew: log.title,
|
// const createdTrack = await WorkTrackModel.create({
|
||||||
appName: log.appName,
|
// vaId,
|
||||||
url: log.url,
|
// projectId,
|
||||||
from: log.from,
|
// logId,
|
||||||
to: log.to, // Fixed: Changed from duplicate "from" to "to"
|
// title: log.title,
|
||||||
totalTime: log.totalTime,
|
// appName: log.appName,
|
||||||
});
|
// url: log.url,
|
||||||
|
// from: log.from,
|
||||||
|
// to: log.to,
|
||||||
|
// totalTime: totalTime,
|
||||||
|
// });
|
||||||
|
|
||||||
return createdTrack;
|
// 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({
|
return res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
message: "Work tracking created",
|
message: "Work tracking created",
|
||||||
data: createdTracks
|
data: createdTrack
|
||||||
});
|
});
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -289,6 +289,129 @@ 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 fetch(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
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,18 @@ async function va_projects_report_function(req, res) {
|
||||||
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);
|
||||||
const vaUsers = await VAModel.find(
|
const vaUsers = await VAModel.find(
|
||||||
|
|
@ -73,55 +85,120 @@ async function va_projects_report_function(req, res) {
|
||||||
|
|
||||||
const projectReport = [];
|
const projectReport = [];
|
||||||
|
|
||||||
for (const vaId of vaIds) {
|
for (const currentVaId of vaIds) {
|
||||||
const vaLogList = logs.filter(l => l.vaId.toString() === vaId);
|
const vaLogList = logs.filter(l => l.vaId.toString() === currentVaId);
|
||||||
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 });
|
sessions.push({ from: currentLogin.time, to: entry.time, logId: currentLogin._id });
|
||||||
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 });
|
sessions.push({ from: currentLogin.time, to: toDate, logId: currentLogin._id });
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaDetails = vaUsers.find(v => v._id.toString() === vaId);
|
const vaDetails = vaUsers.find(v => v._id.toString() === currentVaId);
|
||||||
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: s.from,
|
from: session.from,
|
||||||
to: s.to,
|
to: session.to,
|
||||||
totalSessionTime: formatHMS(sessionWorkMs),
|
totalSessionTime: formatHMS(sessionTotalMs),
|
||||||
tabs,
|
totalSessionMs: sessionTotalMs,
|
||||||
|
workTracksCount: sessionWorkTracks.length,
|
||||||
|
tabs: sessionTabs.sort((a, b) => a.from - b.from), // Sort tabs by time
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -344,15 +344,6 @@ 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,
|
||||||
|
|
@ -365,6 +356,10 @@ const vaWorkTrackSchema = new Schema({
|
||||||
type: Number,
|
type: Number,
|
||||||
default: 0,
|
default: 0,
|
||||||
},
|
},
|
||||||
|
logs: {
|
||||||
|
type: Array,
|
||||||
|
default: [],
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{ timestamps: true }
|
{ timestamps: true }
|
||||||
)
|
)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue