Added days wise dedicated hours in projects

This commit is contained in:
Aakash Mittal 2025-12-17 15:45:59 +05:30
parent 5f043ed80a
commit 46f2d7377f
8 changed files with 134 additions and 114 deletions

View File

@ -1,5 +1,6 @@
import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../../../models/vaModel.js"; import { VAModel, LoggedModel, ClientModel, ProjectModel } from "../../../models/vaModel.js";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import getDayNumber from "../../../utils/getDayNumber.js";
// Getting client dashboard data - all VAs, projects, and active VAs // Getting client dashboard data - all VAs, projects, and active VAs
async function client_dashboard(req, res) { async function client_dashboard(req, res) {
@ -314,9 +315,11 @@ async function client_dashboard(req, res) {
}; };
}); });
const dayNumber = getDayNumber(logTime);
// Calculate dashboard statistics // Calculate dashboard statistics
const totalHoursLogged = clientProjects.reduce((sum, p) => sum + (p.totalLoggedHours || 0), 0); const totalHoursLogged = clientProjects.reduce((sum, p) => sum + (p.totalLoggedHours || 0), 0);
const totalDedicatedHours = clientProjects.reduce((sum, p) => sum + (p.dedicatedHours || 0), 0); const totalDedicatedHours = clientProjects.reduce((sum, p) => sum + (p.dedicatedHours[dayNumber] || 0), 0);
const currentlyActiveVAs = allVAsFormatted.filter(va => va.isCurrentlyActive); const currentlyActiveVAs = allVAsFormatted.filter(va => va.isCurrentlyActive);
const inactiveVAs = allVAsFormatted.filter(va => !va.isCurrentlyActive); const inactiveVAs = allVAsFormatted.filter(va => !va.isCurrentlyActive);

View File

@ -1,5 +1,6 @@
import { ClientModel, ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js"; import { ClientModel, ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import getDayNumber from "../../../utils/getDayNumber.js";
function formatHoursToReadable(hours) { function formatHoursToReadable(hours) {
const wholeHours = Math.floor(hours); const wholeHours = Math.floor(hours);
@ -112,13 +113,15 @@ async function client_dashboard_projects(req, res) {
const validVADetails = assignedVADetails.filter(Boolean); const validVADetails = assignedVADetails.filter(Boolean);
const activeVAsCount = validVADetails.filter(va => va.isActive).length; const activeVAsCount = validVADetails.filter(va => va.isActive).length;
const dayNumber = getDayNumber();
return { return {
id: project._id, id: project._id,
name: project.name, name: project.name,
hourlyRate: project.hourlyRate, hourlyRate: project.hourlyRate,
vaAssigned: validVADetails.length, vaAssigned: validVADetails.length,
activeVAs: activeVAsCount, activeVAs: activeVAsCount,
dedicatedHours: formatHoursToReadable(project.dedicatedHours) || "0h", dedicatedHours: formatHoursToReadable(project.dedicatedHours[dayNumber]) || "0h",
status: project.status || '', status: project.status || '',
assignedVADetails: validVADetails assignedVADetails: validVADetails
}; };

View File

@ -1,7 +1,6 @@
import bcrypt from "bcrypt"; import bcrypt from "bcrypt";
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { ClientModel, VAModel } from "../../models/vaModel.js"; import { ClientModel, VAModel } from "../../models/vaModel.js";
import { va_timesheet_adjustment_function } from "../va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js";
// Login VA // Login VA
async function va_login_client(req, res) { async function va_login_client(req, res) {

View File

@ -4,99 +4,99 @@ import { VAModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js
import authFetch from "../../../utils/authFetch.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) {
try { // try {
const currentTime = new Date(); // const currentTime = new Date();
// Find all open work tracks for this VA and project (where to is not set or totalTime is 0) // // Find all open work tracks for this VA and project (where to is not set or totalTime is 0)
const openWorkTracks = await WorkTrackModel.find({ // const openWorkTracks = await WorkTrackModel.find({
vaId, // vaId,
logId: lastLog._id, // logId: lastLog._id,
$or: [ // $or: [
{ to: { $exists: false } }, // { to: { $exists: false } },
{ totalTime: 0 }, // { totalTime: 0 },
{ to: null } // { to: null }
] // ]
}); // });
// console.log(`Found ${openWorkTracks.length} open work tracks to close`); // console.log(`Found ${openWorkTracks.length} open work tracks to close`);
// Close all open work tracks // // Close all open work tracks
for (const workTrack of openWorkTracks) { // for (const workTrack of openWorkTracks) {
const fromTime = new Date(workTrack.from); // const fromTime = new Date(workTrack.from);
const totalTimeMs = currentTime - fromTime; // const totalTimeMs = currentTime - fromTime;
// Update the work track with end time and total time // // Update the work track with end time and total time
workTrack.to = currentTime; // workTrack.to = currentTime;
workTrack.totalTime = totalTimeMs; // workTrack.totalTime = totalTimeMs;
await workTrack.save(); // await workTrack.save();
// console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`); // // console.log(`Closed work track ${workTrack._id} with duration: ${totalTimeMs}ms`);
} // }
// Create a logout log entry // // Create a logout log entry
const logoutLog = await LoggedModel.create({ // const logoutLog = await LoggedModel.create({
loggedType: "LOGOUT", // loggedType: "LOGOUT",
vaId, // vaId,
vaEmail: lastLog.vaEmail, // Use email from previous log // vaEmail: lastLog.vaEmail, // Use email from previous log
projectId: lastLog.projectId, // projectId: lastLog.projectId,
time: currentTime // Use same timestamp for consistent logging // time: currentTime // Use same timestamp for consistent logging
}); // });
// console.log(`Created logout log: ${logoutLog._id}`); // // console.log(`Created logout log: ${logoutLog._id}`);
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": lastLog.vaEmail, // "vaEmail": lastLog.vaEmail,
"projectId": lastLog.projectId, // "projectId": lastLog.projectId,
"time": logoutLog.time, // "time": logoutLog.time,
} // }
const response = await authFetch(salesforceUrl, { // const response = await authFetch(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 {
closedWorkTracks: openWorkTracks.length,
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 { // return {
// closedWorkTracks: openWorkTracks.length, // closedWorkTracks: openWorkTracks.length,
// logoutLogId: logoutLog._id, // logoutLogId: logoutLog._id,
// SyncSalesforce: result
// }; // };
// } catch (error) {
// console.error("Failed to sync with Salesforce:", error.message);
// console.log(error)
} catch (error) { // // Create a custom error to distinguish Salesforce errors
console.error("Error closing previous session:", error); // const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
throw error; // salesforceError.isSalesforceError = true;
} // throw salesforceError;
} // }
// // return {
// // closedWorkTracks: openWorkTracks.length,
// // logoutLogId: logoutLog._id,
// // };
// } catch (error) {
// console.error("Error closing previous session:", error);
// throw error;
// }
// }
// Login VA // Login VA
async function va_login_callback(req, res) { async function va_login_callback(req, res) {
@ -132,17 +132,17 @@ async function va_login_callback(req, res) {
// also on main login when user try to login into app then logout project and workModel track then allow login on same timestamp // also on main login when user try to login into app then logout project and workModel track then allow login on same timestamp
// Check if VA has any active sessions (LOGIN without LOGOUT) // Check if VA has any active sessions (LOGIN without LOGOUT)
const activeSessions = await LoggedModel.find({ // const activeSessions = await LoggedModel.find({
vaId: user._id // vaId: user._id
}).sort({ time: -1 }); // }).sort({ time: -1 });
let closeResult = []; // let closeResult = [];
if (activeSessions.length > 0) { // if (activeSessions.length > 0) {
// Get the latest login to check if there's no corresponding logout // // Get the latest login to check if there's no corresponding logout
const latestLog = activeSessions[0]; // const latestLog = activeSessions[0];
closeResult = await closePreviousSession(user._id, latestLog); // closeResult = await closePreviousSession(user._id, latestLog);
} // }
// Generate JWT accessToken with 24h expiry // Generate JWT accessToken with 24h expiry
const accessToken = jwt.sign( const accessToken = jwt.sign(
@ -172,23 +172,23 @@ async function va_login_callback(req, res) {
let responseData = []; let responseData = [];
if (closeResult) { // if (closeResult) {
responseData = { // responseData = {
success: true, // success: true,
message: "Login Successfull", // message: "Login Successfull",
accessToken, // accessToken,
refreshToken, // refreshToken,
user: { // user: {
id: user._id, // id: user._id,
name: user.userDetails.firstName, // name: user.userDetails.firstName,
email: user.userDetails.email, // email: user.userDetails.email,
}, // },
previousSession: { // previousSession: {
closedWorkTracks: closeResult.closedWorkTracks, // closedWorkTracks: closeResult.closedWorkTracks,
logoutLogId: closeResult.logoutLogId // logoutLogId: closeResult.logoutLogId
} // }
} // }
} else { // } else {
responseData = { responseData = {
success: true, success: true,
message: "Login Successfull", message: "Login Successfull",
@ -200,7 +200,7 @@ async function va_login_callback(req, res) {
email: user.userDetails.email, email: user.userDetails.email,
} }
} }
} // }
// Return accessToken and user info // Return accessToken and user info
return res.status(200).json(responseData); return res.status(200).json(responseData);

View File

@ -1,5 +1,6 @@
import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js"; import { LoggedModel, WorkTrackModel, ProjectModel, VAModel } from "../../../models/vaModel.js";
import authFetch from "../../../utils/authFetch.js"; import authFetch from "../../../utils/authFetch.js";
import getDayNumber from "../../../utils/getDayNumber.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()) {
@ -429,11 +430,13 @@ async function va_project_log_callback(req, res) {
const totalTodayHours = totalTodayMs / (1000 * 60 * 60); const totalTodayHours = totalTodayMs / (1000 * 60 * 60);
const dayNumber = getDayNumber(logTime);
// Check if daily hours are already completed // Check if daily hours are already completed
if (project.dedicatedHours > 0 && totalTodayHours >= project.dedicatedHours) { if (project.dedicatedHours[dayNumber] > 0 && totalTodayHours >= project.dedicatedHours[dayNumber]) {
return res.status(400).json({ return res.status(400).json({
success: false, success: false,
message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours)})`, message: `Cannot login - daily hours already completed (${formatHoursToReadable(totalTodayHours)} / ${formatHoursToReadable(project.dedicatedHours[dayNumber])})`,
// warnings: scheduleCheck.warnings // warnings: scheduleCheck.warnings
}); });
} }

View File

@ -1,5 +1,6 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js"; import { ProjectModel, VAModel, LoggedModel } from "../../../models/vaModel.js";
import getDayNumber from "../../../utils/getDayNumber.js";
function formatHMS(ms) { function formatHMS(ms) {
const totalSeconds = Math.floor(ms / 1000); const totalSeconds = Math.floor(ms / 1000);
@ -110,13 +111,15 @@ async function va_projects_log_list_function(req, res) {
const lastLog = logs[logs.length - 1]; const lastLog = logs[logs.length - 1];
const activityStatus = lastLog?.loggedType === "LOGIN" ? "ACTIVE" : "AWAY"; const activityStatus = lastLog?.loggedType === "LOGIN" ? "ACTIVE" : "AWAY";
const dayNumber = getDayNumber();
finalReport.push({ finalReport.push({
projectId: project._id, projectId: project._id,
projectName: project.name, projectName: project.name,
activityStatus, activityStatus,
totalWorkTime: formatHMS(totalWorkMs), totalWorkTime: formatHMS(totalWorkMs),
totalWorkMs, totalWorkMs,
dedicatedHours: formatHoursToReadable(project?.dedicatedHours), dedicatedHours: formatHoursToReadable(project?.dedicatedHours[dayNumber]) || "0h",
totalLogs: logs.length, totalLogs: logs.length,
status: project?.status status: project?.status
}); });

View File

@ -1,5 +1,6 @@
import jwt from "jsonwebtoken"; import jwt from "jsonwebtoken";
import { VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js"; import { VAModel, ProjectModel, LoggedModel } from "../../../models/vaModel.js";
import getDayNumber from "../../../utils/getDayNumber.js";
async function va_timesheet_summary_function(req, res) { async function va_timesheet_summary_function(req, res) {
try { try {
@ -162,8 +163,10 @@ async function va_timesheet_summary_function(req, res) {
} }
}; };
const dayNumber = getDayNumber();
// Calculate dedicated hours and pending hours // Calculate dedicated hours and pending hours
const dedicatedHours = project.dedicatedHours || 0; const dedicatedHours = project.dedicatedHours[dayNumber] || 0;
const totalWorkedHours = totalWorkSeconds / 3600; // Convert to hours const totalWorkedHours = totalWorkSeconds / 3600; // Convert to hours
const pendingHours = Math.max(0, dedicatedHours - totalWorkedHours); const pendingHours = Math.max(0, dedicatedHours - totalWorkedHours);

View File

@ -0,0 +1,6 @@
function getDayNumber(date = new Date()) {
const today = new Date(date);
return today.getDay(); // getDay() already returns 0-6 for Sunday-Saturday
}
export default getDayNumber;