211 lines
7.8 KiB
JavaScript
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 }; |