From 5eef945f50fa4ca2b5eed4d636aad2a665edf359 Mon Sep 17 00:00:00 2001 From: Aakash Date: Fri, 21 Nov 2025 15:40:11 +0530 Subject: [PATCH] Added leave manage route --- .../va/vaAuth/vaLogin.controller.js | 46 +++- .../va/vaLeaves/vaLeaveList.controller.js | 101 +++++++++ .../va/vaLeaves/vaLeaveManage.controller.js | 114 ++++++++++ .../va/vaLeaves/vaLeavesApply.controller.js | 209 +++++++++++++++++- .../va/vaLog/vaLogTrackWork.controller.js | 51 ++++- .../va/vaProjects/vaProjectLog.controller.js | 116 ++++++---- .../vaTimesheetAdjustment.controller.js | 85 ++++++- src/models/vaModel.js | 3 +- src/routes/v1/va.routes.js | 13 +- 9 files changed, 669 insertions(+), 69 deletions(-) create mode 100644 src/controllers/va/vaLeaves/vaLeaveList.controller.js create mode 100644 src/controllers/va/vaLeaves/vaLeaveManage.controller.js diff --git a/src/controllers/va/vaAuth/vaLogin.controller.js b/src/controllers/va/vaAuth/vaLogin.controller.js index 08a173b..2bd329f 100644 --- a/src/controllers/va/vaAuth/vaLogin.controller.js +++ b/src/controllers/va/vaAuth/vaLogin.controller.js @@ -42,11 +42,49 @@ async function closePreviousSession(vaId, lastLog) { }); // console.log(`Created logout log: ${logoutLog._id}`); + try { + const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; + const sfDataSync = { + "_id": logoutLog._id, + "loggedType": "LOGOUT", + "vaId": vaId, + "vaEmail": lastLog.vaEmail, + "projectId": lastLog.projectId, + "time": logoutLog.time, + } + + const response = await fetch(salesforceUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add any required headers for Salesforce authentication + // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + }, + body: JSON.stringify(sfDataSync) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); + } + + const result = await response.json(); + // console.log("✅ Successfully synced VA data to Salesforce:", result); - return { - closedWorkTracks: openWorkTracks.length, - logoutLogId: logoutLog._id - }; + 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; + } } catch (error) { console.error("Error closing previous session:", error); diff --git a/src/controllers/va/vaLeaves/vaLeaveList.controller.js b/src/controllers/va/vaLeaves/vaLeaveList.controller.js new file mode 100644 index 0000000..d79a9e1 --- /dev/null +++ b/src/controllers/va/vaLeaves/vaLeaveList.controller.js @@ -0,0 +1,101 @@ +import jwt from "jsonwebtoken"; +import { VAModel, LeavesModel } from "../../../models/vaModel.js"; + +// Get all leave lists applied by VA +async function va_leave_list_function(req, res) { + try { + 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" }); + } + + const vaEmail = decoded.email; + const vaUser = await VAModel.findOne({ "userDetails.email": vaEmail }); + if (!vaUser) { + return res.status(404).json({ success: false, message: "VA not found" }); + } + + const vaId = vaUser._id; + + // Get parameters from route params (they are optional) + const { status, type } = req.params; + + // Get sorting from query params (optional) + const { + sortBy = 'createdAt', + sortOrder = 'desc' + } = req.query; + + // Build filter object + const filter = { vaId }; + + // Handle status filter from params + if (status && status !== 'ALL') { + filter.status = status.toUpperCase(); + } + + // Handle type filter from params (using 'type' instead of 'leaveType') + if (type && type !== 'ALL') { + filter.leaveType = type.toUpperCase(); + } + + // Validate sort parameters + const validSortFields = ['createdAt', 'dateFrom', 'dateTo', 'status', 'leaveType']; + const sortField = validSortFields.includes(sortBy) ? sortBy : 'createdAt'; + const sortDirection = sortOrder.toLowerCase() === 'asc' ? 1 : -1; + + // Get all leaves without pagination + const leaves = await LeavesModel.find(filter) + .sort({ [sortField]: sortDirection }) + .select('-__v') + .lean(); + + // Format the response data + const formattedLeaves = leaves.map(leave => ({ + leaveId: leave._id, + dateFrom: leave.dateFrom.toISOString().split('T')[0], + dateTo: leave.dateTo.toISOString().split('T')[0], + totalDays: parseInt(leave.days), + leaveType: leave.leaveType, + comment: leave.comment, + status: leave.status, + appliedOn: leave.createdAt, + reviewedAt: leave.reviewedAt || null, + rejectionReason: leave.rejectionReason || null + })); + + return res.status(200).json({ + success: true, + message: "Leave list retrieved successfully", + data: { + leaves: formattedLeaves, + totalCount: formattedLeaves.length + } + }); + + } catch (error) { + console.error("Leave list error:", error); + + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ success: false, message: "Invalid token" }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ success: false, message: "Token expired" }); + } + + return res.status(500).json({ + success: false, + message: "Internal Server Error", + error: error.message + }); + } +} + +export { va_leave_list_function }; \ No newline at end of file diff --git a/src/controllers/va/vaLeaves/vaLeaveManage.controller.js b/src/controllers/va/vaLeaves/vaLeaveManage.controller.js new file mode 100644 index 0000000..09bd226 --- /dev/null +++ b/src/controllers/va/vaLeaves/vaLeaveManage.controller.js @@ -0,0 +1,114 @@ +import jwt from "jsonwebtoken"; +import { VAModel, LeavesModel } from "../../../models/vaModel.js"; + +// Approve or reject leave +async function va_leave_manage_function(req, res) { + try { + + const { leaveId } = req.params; + const { action, rejectionReason } = req.body; + + if (!leaveId) { + return res.status(400).json({ + success: false, + message: "Leave ID is required" + }); + } + + if (!action || !['APPROVE', 'REJECT'].includes(action.toUpperCase())) { + return res.status(400).json({ + success: false, + message: "Action is required and must be either 'APPROVE' or 'REJECT'" + }); + } + + // If rejecting, rejectionReason is required + if (action.toUpperCase() === 'REJECT' && !rejectionReason) { + return res.status(400).json({ + success: false, + message: "Rejection reason is required when rejecting a leave" + }); + } + + // Find the leave request + const leaveRequest = await LeavesModel.findById(leaveId); + if (!leaveRequest) { + return res.status(404).json({ + success: false, + message: "Leave request not found" + }); + } + + // Check if leave is already processed + if (leaveRequest.status !== 'PENDING') { + return res.status(400).json({ + success: false, + message: `Leave request is already ${leaveRequest.status.toLowerCase()}` + }); + } + + // Prepare update data + const updateData = { + status: action.toUpperCase() === 'APPROVE' ? 'APPROVED' : 'REJECTED', + reviewedAt: new Date() + }; + + // Add rejection reason if rejecting + if (action.toUpperCase() === 'REJECT') { + updateData.rejectionReason = rejectionReason; + } + + // Update the leave request + const updatedLeave = await LeavesModel.findByIdAndUpdate( + leaveId, + updateData, + { new: true } // Return updated document + ).select('-__v').lean(); + + // Format the response + const formattedLeave = { + leaveId: updatedLeave._id, + dateFrom: updatedLeave.dateFrom.toISOString().split('T')[0], + dateTo: updatedLeave.dateTo.toISOString().split('T')[0], + totalDays: parseInt(updatedLeave.days), + leaveType: updatedLeave.leaveType, + comment: updatedLeave.comment, + status: updatedLeave.status, + appliedOn: updatedLeave.createdAt, + reviewedAt: updatedLeave.reviewedAt, + rejectionReason: updatedLeave.rejectionReason || null + }; + + return res.status(200).json({ + success: true, + message: `Leave request ${action.toLowerCase()}d successfully`, + data: formattedLeave + }); + + } catch (error) { + console.error("Leave approve/reject error:", error); + + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ success: false, message: "Invalid token" }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ success: false, message: "Token expired" }); + } + + if (error.name === 'CastError') { + return res.status(400).json({ + success: false, + message: "Invalid leave ID format" + }); + } + + return res.status(500).json({ + success: false, + message: "Internal Server Error", + error: error.message + }); + } +} + +export { va_leave_manage_function }; \ No newline at end of file diff --git a/src/controllers/va/vaLeaves/vaLeavesApply.controller.js b/src/controllers/va/vaLeaves/vaLeavesApply.controller.js index 6f4d28f..489a6e3 100644 --- a/src/controllers/va/vaLeaves/vaLeavesApply.controller.js +++ b/src/controllers/va/vaLeaves/vaLeavesApply.controller.js @@ -1,7 +1,7 @@ import jwt from "jsonwebtoken"; import { VAModel, LeavesModel } from "../../../models/vaModel.js"; -// Adjust missing hours by creating new records +// Apply for leave async function va_leave_apply_function(req, res) { try { const accessToken = req.headers.token; @@ -22,20 +22,219 @@ async function va_leave_apply_function(req, res) { } const vaId = vaUser._id; - const { dateFrom, dateTo, days, leaveType, comment } = req.body; + const { dateFrom, dateTo, leaveType, comment } = req.body; - if (!dateFrom || !dateTo || !days || !leaveType || !comment) { + if (!dateFrom || !dateTo || !leaveType || !comment) { return res.status(400).json({ success: false, - message: "dateFrom, dateTo, days, leaveType, and comment are required" + message: "dateFrom, dateTo, leaveType, and comment are required" }); } + // Validate date formats + let startDate, endDate; + try { + startDate = new Date(dateFrom); + endDate = new Date(dateTo); + + // Set to start of day for accurate calculation + startDate.setHours(0, 0, 0, 0); + endDate.setHours(0, 0, 0, 0); + + if (isNaN(startDate.getTime()) || isNaN(endDate.getTime())) { + return res.status(400).json({ + success: false, + message: "Invalid date format. Use valid date format (YYYY-MM-DD)" + }); + } + } catch (error) { + return res.status(400).json({ + success: false, + message: "Invalid date format" + }); + } + + // Ensure start date is before or equal to end date + if (startDate > endDate) { + return res.status(400).json({ + success: false, + message: "Start date must be before or equal to end date" + }); + } + + // Ensure dates are not in the past + const today = new Date(); + today.setHours(0, 0, 0, 0); + if (startDate < today) { + return res.status(400).json({ + success: false, + message: "Cannot apply for leave for past dates" + }); + } + + // Validate leave type + const validLeaveTypes = ["SICK", "PTO"]; + if (!validLeaveTypes.includes(leaveType)) { + return res.status(400).json({ + success: false, + message: "Invalid leave type. Must be SICK or PTO" + }); + } + + // Calculate total days (inclusive of both start and end dates) + const timeDifference = endDate.getTime() - startDate.getTime(); + const totalDays = Math.floor(timeDifference / (1000 * 60 * 60 * 24)) + 1; // +1 to include both start and end dates + + if (totalDays <= 0) { + return res.status(400).json({ + success: false, + message: "Invalid date range" + }); + } + + // Check for overlapping leave requests + const existingLeave = await LeavesModel.findOne({ + vaId, + status: "PENDING", + $or: [ + { + dateFrom: { $lte: endDate }, + dateTo: { $gte: startDate } + } + ] + }); + + if (existingLeave) { + return res.status(400).json({ + success: false, + message: "You already have a pending leave request that overlaps with these dates" + }); + } + + // Check for overlapping approved leaves + const existingApprovedLeave = await LeavesModel.findOne({ + vaId, + status: "APPROVED", + $or: [ + { + dateFrom: { $lte: endDate }, + dateTo: { $gte: startDate } + } + ] + }); + + if (existingApprovedLeave) { + return res.status(400).json({ + success: false, + message: "You already have an approved leave that overlaps with these dates" + }); + } + + // Create leave request + const leaveRequest = new LeavesModel({ + vaId, + dateFrom: startDate, + dateTo: endDate, + days: totalDays.toString(), + leaveType, + comment, + status: "PENDING" + }); + + await leaveRequest.save(); + + // try { + // const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/timesheet-adjustment`; + // const sfData = { + // "id": adjustmentRequest._id, + // "va": { + // "id": vaId + // }, + // "project": { + // "id": projectId + // }, + // "date": adjustmentDate.toISOString().split('T')[0], + // "loginTime": loginDateTime.toISOString(), + // "logoutTime": logoutDateTime.toISOString(), + // "comment": comment, + // "status": "PENDING", + // "rejectionReason": "", + // "adjustedHours": formatTime(adjustedSeconds) + // } + + // const response = await fetch(salesforceUrl, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // // Add any required headers for Salesforce authentication + // // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + // }, + // body: JSON.stringify({"data": 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(); + // // console.log("✅ Successfully synced VA data to Salesforce:", result); + + // return res.status(201).json({ + // success: true, + // message: "Adjustment request submitted successfully", + // data: { + // adjustmentId: adjustmentRequest._id, + // date: adjustmentDate.toISOString().split('T')[0], + // loginTime: loginDateTime.toISOString(), + // logoutTime: logoutDateTime.toISOString(), + // adjustedHours: formatTime(adjustedSeconds), + // comment: comment, + // status: "PENDING" + // }, + // 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 res.status(201).json({ + success: true, + message: "Leave request submitted successfully", + data: { + leaveId: leaveRequest._id, + dateFrom: startDate.toISOString().split('T')[0], + dateTo: endDate.toISOString().split('T')[0], + totalDays: totalDays, + leaveType, + comment, + status: "PENDING" + } + }); } catch (error) { console.error("Leave apply error:", error); - return res.status(500).json({ success: false, message: "Internal Server Error" }); + + if (error.name === 'JsonWebTokenError') { + return res.status(401).json({ success: false, message: "Invalid token" }); + } + + if (error.name === 'TokenExpiredError') { + return res.status(401).json({ success: false, message: "Token expired" }); + } + + return res.status(500).json({ + success: false, + message: "Internal Server Error", + error: error.message + }); } } diff --git a/src/controllers/va/vaLog/vaLogTrackWork.controller.js b/src/controllers/va/vaLog/vaLogTrackWork.controller.js index 89366f6..ddf5d22 100644 --- a/src/controllers/va/vaLog/vaLogTrackWork.controller.js +++ b/src/controllers/va/vaLog/vaLogTrackWork.controller.js @@ -65,13 +65,52 @@ async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, curre vaEmail, projectId }); + + try { + const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; + const sfDataSync = { + "_id": logoutLog._id, + "loggedType": "LOGOUT", + "vaId": vaId, + "vaEmail": vaEmail, + "projectId": projectId, + "time": logoutLog.time, + } + + const response = await fetch(salesforceUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add any required headers for Salesforce authentication + // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + }, + body: JSON.stringify(sfDataSync) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); + } + + const result = await response.json(); + // console.log("✅ Successfully synced VA data to Salesforce:", result); - return { - shouldLogout: true, - totalTodayHours: totalTodayHours, - dedicatedHours: project.dedicatedHours, - logoutLogId: logoutLog._id - }; + return { + shouldLogout: true, + totalTodayHours: totalTodayHours, + dedicatedHours: project.dedicatedHours, + 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 { diff --git a/src/controllers/va/vaProjects/vaProjectLog.controller.js b/src/controllers/va/vaProjects/vaProjectLog.controller.js index a06daaa..1fcc9c6 100644 --- a/src/controllers/va/vaProjects/vaProjectLog.controller.js +++ b/src/controllers/va/vaProjects/vaProjectLog.controller.js @@ -66,11 +66,49 @@ async function closePreviousSession(vaId, lastLog) { }); // console.log(`Created logout log: ${logoutLog._id}`); + try { + const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/va-project-log`; + const sfDataSync = { + "_id": logoutLog._id, + "loggedType": "LOGOUT", + "vaId": vaId, + "vaEmail": lastLog.vaEmail, + "projectId": lastLog.projectId, + "time": logoutLog.time, + } + + const response = await fetch(salesforceUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add any required headers for Salesforce authentication + // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + }, + body: JSON.stringify(sfDataSync) + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); + } + + const result = await response.json(); + // console.log("✅ Successfully synced VA data to Salesforce:", result); - return { - closedWorkTracks: openWorkTracks.length, - logoutLogId: logoutLog._id - }; + 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; + } } catch (error) { console.error("Error closing previous session:", error); @@ -151,47 +189,47 @@ async function va_project_log_callback(req, res) { const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId }) // console.log(createdLog) - 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, - } + // 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', - // Add any required headers for Salesforce authentication - // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN - }, - body: JSON.stringify(sfData) - }); + // const response = await fetch(salesforceUrl, { + // method: 'POST', + // headers: { + // 'Content-Type': 'application/json', + // // Add any required headers for Salesforce authentication + // // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + // }, + // body: JSON.stringify(sfData) + // }); - if (!response.ok) { - const errorText = await response.text(); - throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); - } + // if (!response.ok) { + // const errorText = await response.text(); + // throw new Error(`Salesforce API responded with status ${response.status}: ${errorText}`); + // } - const result = await response.json(); - // console.log("✅ Successfully synced VA data to Salesforce:", result); + // 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 }); - } catch (error) { - console.error("Failed to sync with Salesforce:", error.message); - console.log(error) + // res.status(201).json({ success: true, message: `${type} Successfully`, logId: createdLog._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; - } + // // Create a custom error to distinguish Salesforce errors + // const salesforceError = new Error(`Salesforce sync failed: ${error.message}`); + // salesforceError.isSalesforceError = true; + // throw salesforceError; + // } - // return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); + return res.status(201).send({ success: true, message: `${type} Successfully`, logId: createdLog._id }); } catch (error) { return res.status(500).send("Internal Server Error"); diff --git a/src/controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js b/src/controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js index d659168..6931442 100644 --- a/src/controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js +++ b/src/controllers/va/vaTimesheetAdjustment/vaTimesheetAdjustment.controller.js @@ -140,19 +140,80 @@ async function va_timesheet_adjustment_function(req, res) { return `${h}h ${m}m ${s}s`; }; - return res.status(201).json({ - success: true, - message: "Adjustment request submitted successfully", - data: { - adjustmentId: adjustmentRequest._id, - date: adjustmentDate.toISOString().split('T')[0], - loginTime: loginDateTime.toISOString(), - logoutTime: logoutDateTime.toISOString(), - adjustedHours: formatTime(adjustedSeconds), - comment: comment, - status: "PENDING" + try { + const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/timesheet-adjustment`; + const sfData = { + "id": adjustmentRequest._id, + "va": { + "id": vaId + }, + "project": { + "id": projectId + }, + "date": adjustmentDate.toISOString().split('T')[0], + "loginTime": loginDateTime.toISOString(), + "logoutTime": logoutDateTime.toISOString(), + "comment": comment, + "status": "PENDING", + "rejectionReason": "", + "adjustedHours": formatTime(adjustedSeconds) } - }); + + const response = await fetch(salesforceUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + // Add any required headers for Salesforce authentication + // 'Authorization': 'Bearer ' + process.env.SF_ACCESS_TOKEN + }, + body: JSON.stringify({"data": 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(); + // console.log("✅ Successfully synced VA data to Salesforce:", result); + + return res.status(201).json({ + success: true, + message: "Adjustment request submitted successfully", + data: { + adjustmentId: adjustmentRequest._id, + date: adjustmentDate.toISOString().split('T')[0], + loginTime: loginDateTime.toISOString(), + logoutTime: logoutDateTime.toISOString(), + adjustedHours: formatTime(adjustedSeconds), + comment: comment, + status: "PENDING" + }, + 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 res.status(201).json({ + // success: true, + // message: "Adjustment request submitted successfully", + // data: { + // adjustmentId: adjustmentRequest._id, + // date: adjustmentDate.toISOString().split('T')[0], + // loginTime: loginDateTime.toISOString(), + // logoutTime: logoutDateTime.toISOString(), + // adjustedHours: formatTime(adjustedSeconds), + // comment: comment, + // status: "PENDING" + // } + // }); } catch (error) { console.error("Timesheet adjustment error:", error); diff --git a/src/models/vaModel.js b/src/models/vaModel.js index dc2f6e9..b33241f 100644 --- a/src/models/vaModel.js +++ b/src/models/vaModel.js @@ -432,8 +432,7 @@ const leavesSchema = new Schema({ }, clientId: { type: Types.ObjectId, - ref: "Client", - required: true + ref: "Client" }, dateFrom: { type: Date, diff --git a/src/routes/v1/va.routes.js b/src/routes/v1/va.routes.js index 0686e9d..087e684 100644 --- a/src/routes/v1/va.routes.js +++ b/src/routes/v1/va.routes.js @@ -23,6 +23,9 @@ import { va_timesheet_adjustment_function } from "../../controllers/va/vaTimeshe import { get_adjustment_requests_list } from "../../controllers/va/vaTimesheetAdjustment/vaAllTimesheetAdjustmentReuqest.controller.js" import { va_timesheet_manage_adjustment_request } from "../../controllers/va/vaTimesheetAdjustment/vaManageTimesheetAdjustment.controller.js" import { va_verify_email } from "../../controllers/va/vaAuth/vaVerifyEmail.controller.js"; +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"; vaRoutes.get("/", va_callback); // VA Callback for getting active VA @@ -43,9 +46,17 @@ vaRoutes.get("/:userName", va_username_callback); // 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 vaRoutes.post("/timesheet/adjustment/request", authValidator, va_timesheet_adjustment_function); // Timesheet Adjustment for VA -vaRoutes.get("/timesheet/adjustments/list/:status?/:projectId?/:vaId?", get_adjustment_requests_list); // show all adjustment list to approve and reject +// vaRoutes.get("/timesheet/adjustments/list/:status?/:projectId?/:vaId?", get_adjustment_requests_list); // show all adjustment list to approve and reject vaRoutes.patch("/timesheet/adjustments/:adjustmentId", va_timesheet_manage_adjustment_request); +// Leaves routes +vaRoutes.get("/leaves/all", authValidator, va_leave_list_function); // Get all leaves +vaRoutes.get("/leaves/status/:status", authValidator, va_leave_list_function); // Leaves Filter by status +vaRoutes.get("/leaves/type/:leaveType", authValidator, va_leave_list_function); // Filter by leave type +vaRoutes.get("/leaves/status/:status/type/:leaveType", authValidator, va_leave_list_function); // leaves Filter +vaRoutes.post("/leave/request", authValidator, va_leave_apply_function); // Timesheet Adjustment for VA +vaRoutes.patch("/leave/:leaveId", va_leave_manage_function); + // timesheet adjustment routes // Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)