diff --git a/public/js/va-client-register.js b/public/js/va-client-register.js index a9d6788..7c330b2 100644 --- a/public/js/va-client-register.js +++ b/public/js/va-client-register.js @@ -693,12 +693,12 @@ async function submitForm() { let cardNumber = document.getElementById('card-number'); let expiryDate = document.getElementById('expiry-date'); let securityCode = document.getElementById('security-code'); - let cardZip = document.getElementById('card-zip').value; - let bankName = document.getElementById('bank-name').value; - let accountNumber = document.getElementById('account-number').value; - let routingNumber = document.getElementById('routing-number').value; - let accountType = document.getElementById('account-type').value; - let accountHolderName = document.getElementById('account-holder-name').value; + let cardZip = document.getElementById('card-zip'); + let bankName = document.getElementById('bank-name'); + let accountNumber = document.getElementById('account-number'); + let routingNumber = document.getElementById('routing-number'); + let accountType = document.getElementById('account-type'); + let accountHolderName = document.getElementById('account-holder-name'); if(cardNumber.value) { formData.userDetails.cardNumber = cardNumber.value; diff --git a/src/controllers/project/deleteProjectSf.controller.js b/src/controllers/project/deleteProjectSf.controller.js new file mode 100644 index 0000000..c3c6e39 --- /dev/null +++ b/src/controllers/project/deleteProjectSf.controller.js @@ -0,0 +1,97 @@ +import { ProjectModel, VAModel, ClientModel } from "../../models/vaModel.js"; +import { Types } from "mongoose"; + +/** + * Delete project and all related data (without transaction) + */ +async function delete_project_sf(req, res) { + try { + const { projectId } = req.params; + + // Validate projectId + if (!projectId) { + return res.status(400).json({ + success: false, + message: "Project ID is required" + }); + } + + let projectObjectId; + try { + projectObjectId = new Types.ObjectId(projectId); + } catch (error) { + return res.status(400).json({ + success: false, + message: "Invalid Project ID format" + }); + } + + // Check if project exists + const existingProject = await ProjectModel.findById(projectObjectId); + if (!existingProject) { + return res.status(404).json({ + success: false, + message: "Project not found" + }); + } + + const clientId = existingProject.clientId; + const vaAssigned = existingProject.vaAssigned || []; + const projectName = existingProject.name; + + console.log(`Deleting project: ${projectName} (${projectId})`); + + const deletionSummary = {}; + + // 1. Remove project from Client's projectIds array + if (clientId) { + await ClientModel.findByIdAndUpdate( + clientId, + { $pull: { projectIds: projectObjectId } } + ); + deletionSummary.clientUpdated = true; + console.log(`Removed project from client`); + } + + // 2. Remove project from all assigned VAs' projectIds arrays + if (vaAssigned.length > 0) { + const vaResult = await VAModel.updateMany( + { _id: { $in: vaAssigned } }, + { $pull: { projectIds: projectObjectId } } + ); + deletionSummary.vasUpdated = vaResult.modifiedCount; + console.log(`Removed project from ${deletionSummary.vasUpdated} VAs`); + } else { + deletionSummary.vasUpdated = 0; + } + + // 6. Finally delete the project + await ProjectModel.findByIdAndDelete(projectObjectId); + + console.log(`✅ Successfully deleted project: ${projectName} (${projectId})`); + + res.status(200).json({ + success: true, + message: 'Project and all related data deleted successfully', + data: { + projectId: projectId, + projectName: projectName, + clientId: clientId, + deletedAt: new Date().toISOString() + } + }); + + } catch (error) { + console.error("delete_project_sf error:", error); + + return res.status(500).json({ + success: false, + message: "Failed to delete project", + error: process.env.NODE_ENV === 'production' + ? 'Internal server error' + : error.message + }); + } +} + +export { delete_project_sf }; \ No newline at end of file diff --git a/src/controllers/va/vaLog/vaLogTrackWork.controller.js b/src/controllers/va/vaLog/vaLogTrackWork.controller.js index ddf5d22..8333dfd 100644 --- a/src/controllers/va/vaLog/vaLogTrackWork.controller.js +++ b/src/controllers/va/vaLog/vaLogTrackWork.controller.js @@ -147,7 +147,7 @@ async function va_log_track_work_callback(req, res) { const vaId = vaUser._id; const payload = req.body || {}; - const { projectId, logId, title, appName } = payload; + const { projectId, logId, title, appName, url = null } = payload; const project = await ProjectModel.findById(projectId); if (!project) return res.status(400).json({ success: false, message: "Project not found" }); @@ -190,6 +190,7 @@ async function va_log_track_work_callback(req, res) { logId, title, appName, + url, from: Date.now(), }); diff --git a/src/controllers/va/vaProfile/vaDelete.controller.js b/src/controllers/va/vaProfile/vaDelete.controller.js new file mode 100644 index 0000000..8b71c12 --- /dev/null +++ b/src/controllers/va/vaProfile/vaDelete.controller.js @@ -0,0 +1,92 @@ +import { VAModel, ClientModel, ProjectModel, LoggedModel, WorkTrackModel } from "../../../models/vaModel.js"; + +// Delete VA and all related data +async function va_remove_function(req, res) { + try { + const vaId = req.params.id; + + // Validate VA ID + if (!vaId) { + return res.status(400).json({ + success: false, + message: "VA ID is required" + }); + } + + // Find the VA to get clientIds and projectIds before deletion + const vaUser = await VAModel.findById(vaId); + if (!vaUser) { + return res.status(404).json({ + success: false, + message: "VA not found" + }); + } + + const clientIds = vaUser.clientIds || []; + const projectIds = vaUser.projectIds || []; + const vaEmail = vaUser.userDetails?.email; + + console.log(`Deleting VA ${vaId} - Affected: ${clientIds.length} clients, ${projectIds.length} projects`); + + // 1. Remove VA from all Clients + let clientsUpdated = 0; + if (clientIds.length > 0) { + const clientResult = await ClientModel.updateMany( + { _id: { $in: clientIds } }, + { $pull: { vaIds: vaId } } + ); + clientsUpdated = clientResult.modifiedCount; + console.log(`Removed VA from ${clientsUpdated} clients`); + } + + // 2. Remove VA from all Projects + let projectsUpdated = 0; + if (projectIds.length > 0) { + const projectResult = await ProjectModel.updateMany( + { _id: { $in: projectIds } }, + { $pull: { vaAssigned: vaId } } + ); + projectsUpdated = projectResult.modifiedCount; + console.log(`Removed VA from ${projectsUpdated} projects`); + } + + // 3. Delete all WorkTrack records for this VA + const workTrackResult = await WorkTrackModel.deleteMany({ vaId: vaId }); + console.log(`Deleted ${workTrackResult.deletedCount} work track records`); + + // 4. Delete all Logged records for this VA + const loggedResult = await LoggedModel.deleteMany({ vaId: vaId }); + console.log(`Deleted ${loggedResult.deletedCount} log records`); + + // 5. Finally delete the VA + await VAModel.findByIdAndDelete(vaId); + + console.log(`Successfully deleted VA: ${vaId} and all related data`); + + res.status(200).json({ + success: true, + message: 'Virtual Assistant and all related data deleted successfully', + data: { + vaId: vaId, + vaName: `${vaUser.userDetails?.firstName || ''} ${vaUser.userDetails?.lastName || ''}`.trim() || 'Unknown VA', + vaEmail: vaEmail, + clientsUpdated: clientsUpdated, + projectsUpdated: projectsUpdated, + workTracksDeleted: workTrackResult.deletedCount, + logsDeleted: loggedResult.deletedCount, + deletedAt: new Date().toISOString() + } + }); + + } catch (error) { + console.error('Error deleting VA:', error); + + res.status(500).json({ + success: false, + message: 'Failed to delete Virtual Assistant', + error: 'Internal server error' + }); + } +} + +export { va_remove_function }; \ No newline at end of file diff --git a/src/controllers/va/vaProjects/vaProjectLog.controller.js b/src/controllers/va/vaProjects/vaProjectLog.controller.js index 1f62636..07ca354 100644 --- a/src/controllers/va/vaProjects/vaProjectLog.controller.js +++ b/src/controllers/va/vaProjects/vaProjectLog.controller.js @@ -1,12 +1,12 @@ import { LoggedModel, WorkTrackModel, ProjectModel } from "../../../models/vaModel.js"; -// Helper function to get start and end of day +// Helper function to get start and end of day in UTC function getDayRange(date = new Date()) { const start = new Date(date); - start.setHours(0, 0, 0, 0); + start.setUTCHours(0, 0, 0, 0); const end = new Date(date); - end.setHours(23, 59, 59, 999); + end.setUTCHours(23, 59, 59, 999); return { start, end }; } @@ -25,10 +25,53 @@ function formatHoursToReadable(hours) { } } -// function to close open work tracks and create logout -async function closePreviousSession(vaId, lastLog) { +// Helper function to parse ISO time string +function parseISOTimeString(timeString) { + if (!timeString) return new Date(); + try { - const currentTime = new Date(); + // Parse ISO string directly + const parsedDate = new Date(timeString); + + // Check if the date is valid + if (isNaN(parsedDate.getTime())) { + throw new Error('Invalid date'); + } + parsedDate.setMilliseconds(0); + + console.log("pasdes date" + parsedDate) + return parsedDate; + } catch (error) { + console.error('Error parsing ISO time string:', error); + return new Date(); + } +} + +function setMillisecondsToZero(date) { + const newDate = new Date(date); + newDate.setMilliseconds(0); + return newDate; +} + +// Helper function to check if time difference is less than threshold +function isTimeWithinThreshold(receivedTime, currentTime, thresholdSeconds = 10) { + const diffMs = Math.abs(currentTime - receivedTime); + const diffSeconds = diffMs / 1000; + console.log(`Time comparison - Received: ${receivedTime.toISOString()}, Current: ${currentTime.toISOString()}, Diff: ${diffSeconds}s`); + return diffSeconds <= thresholdSeconds; +} + +// Helper function to get current UTC time +function getCurrentUTCTime() { + const now = new Date(); + now.setMilliseconds(0); + return now; +} + +// function to close open work tracks and create logout +async function closePreviousSession(vaId, lastLog, logTime = new Date()) { + try { + const currentTime = logTime; // Find all open work tracks for this VA and project (where to is not set or totalTime is 0) const openWorkTracks = await WorkTrackModel.find({ @@ -120,18 +163,44 @@ async function closePreviousSession(vaId, lastLog) { async function va_project_log_callback(req, res) { try { const payload = req.body || {}; - const { vaId, vaEmail, projectId, type } = payload; + const { vaId, vaEmail, projectId, type, time: receivedTimeString } = payload; + console.log("Received payload:", payload); - if (!vaId, !vaEmail, !projectId, !type) return res.status(400).send("Missing Props"); + if (!vaId || !vaEmail || !projectId || !type) { + return res.status(400).send("Missing required properties"); + } + + // Determine the log time based on received time (both in UTC) + const currentTime = getCurrentUTCTime(); // Current UTC time + let logTime = currentTime; + + if (receivedTimeString) { + // Parse the received ISO time string + const receivedTime = parseISOTimeString(receivedTimeString); + + // Check if the difference is less than 10 seconds + if (isTimeWithinThreshold(receivedTime, currentTime, 10)) { + logTime = receivedTime; + console.log(`✅ Using received ISO time: ${receivedTimeString}, difference is within 10 seconds`); + console.log(`✅ Parsed as: ${receivedTime.toISOString()}`); + } else { + logTime = currentTime; + console.log(`⚠️ Using current UTC time, received time difference exceeds 10 seconds.`); + console.log(`Received: ${receivedTimeString} (parsed: ${receivedTime.toISOString()})`); + console.log(`Current: ${currentTime.toISOString()}`); + } + } else { + console.log(`ℹ️ No time received, using current UTC time: ${currentTime.toISOString()}`); + } const project = await ProjectModel.findById(projectId); if (!project) { return res.status(400).json({ success: false, message: "Project not found" }); } - // For LOGIN requests, check daily hours + // For LOGIN requests, check daily hours (using UTC day range) if (type === "LOGIN") { - const { start, end } = getDayRange(); + const { start, end } = getDayRange(); // UTC day range // Get today's total hours const todayWorkEntries = await WorkTrackModel.find({ vaId, @@ -163,7 +232,7 @@ async function va_project_log_callback(req, res) { // check last workModel login time and make a logout request then create login // console.log(`User already logged in. Closing previous session...`); - const closeResult = await closePreviousSession(vaId, lastLog); + const closeResult = await closePreviousSession(vaId, lastLog, logTime); // console.log(`Previous session closed: ${closeResult.closedWorkTracks} work tracks, logout log: ${closeResult.logoutLogId}`); @@ -172,7 +241,8 @@ async function va_project_log_callback(req, res) { loggedType: "LOGIN", vaId, vaEmail, - projectId + projectId, + time: logTime }); return res.status(201).send({ @@ -182,13 +252,21 @@ async function va_project_log_callback(req, res) { previousSession: { closedWorkTracks: closeResult.closedWorkTracks, logoutLogId: closeResult.logoutLogId - } + }, + timeUsed: receivedTimeString ? 'received' : 'current', + actualTime: createdLog.time }); } } - const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) -// console.log(createdLog) + 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 = { @@ -218,7 +296,14 @@ async function va_project_log_callback(req, res) { const result = await response.json(); // console.log("✅ Successfully synced VA data to Salesforce:", result); - res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._id, SyncSalesforce: result }); + 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) @@ -229,9 +314,8 @@ async function va_project_log_callback(req, res) { throw salesforceError; } - // return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); - } catch (error) { + console.error("Error in va_project_log_callback:", error); return res.status(500).send("Internal Server Error"); } } diff --git a/src/models/vaModel.js b/src/models/vaModel.js index b33241f..b16190f 100644 --- a/src/models/vaModel.js +++ b/src/models/vaModel.js @@ -320,6 +320,9 @@ const vaWorkTrackSchema = new Schema({ appName: { type: String, }, + url: { + type: String, + }, from: { type: Date, default: Date.now, diff --git a/src/routes/v1/index.js b/src/routes/v1/index.js index a426f60..4cd658a 100644 --- a/src/routes/v1/index.js +++ b/src/routes/v1/index.js @@ -12,6 +12,7 @@ import { process_reset } from "../../controllers/passwordReset.controllers.js"; import { project_add_sf } from "../../controllers/project/projectAddSf.controller.js" import { update_project_sf } from "../../controllers/project/projectUpdateSf.controller.js" import { authValidator } from "../../middlewares/auth.middleware.js"; +import { delete_project_sf } from "../../controllers/project/deleteProjectSf.controller.js" const v1Routes = express.Router() @@ -29,6 +30,7 @@ v1Routes.use("/client", clientRoutes); v1Routes.post("/project", authValidator, project_add_sf); v1Routes.patch("/project/:projectId", authValidator, update_project_sf); +v1Routes.delete("/project/:projectId", authValidator, delete_project_sf); v1Routes.post("/password/reset", process_reset); diff --git a/src/routes/v1/va.routes.js b/src/routes/v1/va.routes.js index 087e684..fa8549d 100644 --- a/src/routes/v1/va.routes.js +++ b/src/routes/v1/va.routes.js @@ -26,6 +26,7 @@ import { va_verify_email } from "../../controllers/va/vaAuth/vaVerifyEmail.contr import { va_leave_apply_function } from "../../controllers/va/vaLeaves/vaLeavesApply.controller.js"; import { va_leave_list_function } from "../../controllers/va/vaLeaves/vaLeaveList.controller.js" import { va_leave_manage_function } from "../../controllers/va/vaLeaves/vaLeaveManage.controller.js"; +import { va_remove_function } from "../../controllers/va/vaProfile/vaDelete.controller.js"; vaRoutes.get("/", va_callback); // VA Callback for getting active VA @@ -42,6 +43,7 @@ vaRoutes.patch("/profile", authValidator, va_profile_update_callback); // Update vaRoutes.get("/:userName", va_username_callback); // VA Username Callback +vaRoutes.delete("/:id", va_remove_function); // VA Username Callback // timesheet adjustment routes vaRoutes.get("/timesheet/summary/:projectId/:date", authValidator, va_timesheet_summary_function); // Get Timesheet Adjustment for VA on specific project and date