va-tracker-middleware/src/controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustmentSummar...

211 lines
7.8 KiB
JavaScript

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