Added leave manage route
This commit is contained in:
parent
a48a919b41
commit
5eef945f50
|
|
@ -42,11 +42,49 @@ async function closePreviousSession(vaId, lastLog) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(`Created logout log: ${logoutLog._id}`);
|
// 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 {
|
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)
|
||||||
|
|
||||||
|
// Create a custom error to distinguish Salesforce errors
|
||||||
|
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||||
|
salesforceError.isSalesforceError = true;
|
||||||
|
throw salesforceError;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error closing previous session:", error);
|
console.error("Error closing previous session:", error);
|
||||||
|
|
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -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 };
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
import jwt from "jsonwebtoken";
|
import jwt from "jsonwebtoken";
|
||||||
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
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) {
|
async function va_leave_apply_function(req, res) {
|
||||||
try {
|
try {
|
||||||
const accessToken = req.headers.token;
|
const accessToken = req.headers.token;
|
||||||
|
|
@ -22,20 +22,219 @@ async function va_leave_apply_function(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const vaId = vaUser._id;
|
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({
|
return res.status(400).json({
|
||||||
success: false,
|
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) {
|
} catch (error) {
|
||||||
console.error("Leave apply error:", 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
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -65,13 +65,52 @@ async function checkDailyHoursAndLogout(vaId, vaEmail, projectId, project, curre
|
||||||
vaEmail,
|
vaEmail,
|
||||||
projectId
|
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 {
|
return {
|
||||||
shouldLogout: true,
|
shouldLogout: true,
|
||||||
totalTodayHours: totalTodayHours,
|
totalTodayHours: totalTodayHours,
|
||||||
dedicatedHours: project.dedicatedHours,
|
dedicatedHours: project.dedicatedHours,
|
||||||
logoutLogId: logoutLog._id
|
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 {
|
||||||
|
|
|
||||||
|
|
@ -66,11 +66,49 @@ async function closePreviousSession(vaId, lastLog) {
|
||||||
});
|
});
|
||||||
|
|
||||||
// console.log(`Created logout log: ${logoutLog._id}`);
|
// 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 {
|
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)
|
||||||
|
|
||||||
|
// Create a custom error to distinguish Salesforce errors
|
||||||
|
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||||
|
salesforceError.isSalesforceError = true;
|
||||||
|
throw salesforceError;
|
||||||
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("Error closing previous session:", 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 })
|
const createdLog = await LoggedModel.create({ loggedType: type, vaId, vaEmail, projectId })
|
||||||
// console.log(createdLog)
|
// console.log(createdLog)
|
||||||
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 sfData = {
|
// const sfData = {
|
||||||
"_id": createdLog._id,
|
// "_id": createdLog._id,
|
||||||
"loggedType": type,
|
// "loggedType": type,
|
||||||
"vaId": vaId,
|
// "vaId": vaId,
|
||||||
"vaEmail": vaEmail,
|
// "vaEmail": vaEmail,
|
||||||
"projectId": projectId,
|
// "projectId": projectId,
|
||||||
"time": createdLog.time,
|
// "time": createdLog.time,
|
||||||
}
|
// }
|
||||||
|
|
||||||
const response = await fetch(salesforceUrl, {
|
// const response = await fetch(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(sfData)
|
// body: JSON.stringify(sfData)
|
||||||
});
|
// });
|
||||||
|
|
||||||
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);
|
||||||
|
|
||||||
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 });
|
||||||
} catch (error) {
|
// } catch (error) {
|
||||||
console.error("Failed to sync with Salesforce:", error.message);
|
// console.error("Failed to sync with Salesforce:", error.message);
|
||||||
console.log(error)
|
// console.log(error)
|
||||||
|
|
||||||
// Create a custom error to distinguish Salesforce errors
|
// // Create a custom error to distinguish Salesforce errors
|
||||||
const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
// const salesforceError = new Error(`Salesforce sync failed: ${error.message}`);
|
||||||
salesforceError.isSalesforceError = true;
|
// salesforceError.isSalesforceError = true;
|
||||||
throw salesforceError;
|
// 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) {
|
} catch (error) {
|
||||||
return res.status(500).send("Internal Server Error");
|
return res.status(500).send("Internal Server Error");
|
||||||
|
|
|
||||||
|
|
@ -140,19 +140,80 @@ async function va_timesheet_adjustment_function(req, res) {
|
||||||
return `${h}h ${m}m ${s}s`;
|
return `${h}h ${m}m ${s}s`;
|
||||||
};
|
};
|
||||||
|
|
||||||
return res.status(201).json({
|
try {
|
||||||
success: true,
|
const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/timesheet-adjustment`;
|
||||||
message: "Adjustment request submitted successfully",
|
const sfData = {
|
||||||
data: {
|
"id": adjustmentRequest._id,
|
||||||
adjustmentId: adjustmentRequest._id,
|
"va": {
|
||||||
date: adjustmentDate.toISOString().split('T')[0],
|
"id": vaId
|
||||||
loginTime: loginDateTime.toISOString(),
|
},
|
||||||
logoutTime: logoutDateTime.toISOString(),
|
"project": {
|
||||||
adjustedHours: formatTime(adjustedSeconds),
|
"id": projectId
|
||||||
comment: comment,
|
},
|
||||||
status: "PENDING"
|
"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) {
|
} catch (error) {
|
||||||
console.error("Timesheet adjustment error:", error);
|
console.error("Timesheet adjustment error:", error);
|
||||||
|
|
|
||||||
|
|
@ -432,8 +432,7 @@ const leavesSchema = new Schema({
|
||||||
},
|
},
|
||||||
clientId: {
|
clientId: {
|
||||||
type: Types.ObjectId,
|
type: Types.ObjectId,
|
||||||
ref: "Client",
|
ref: "Client"
|
||||||
required: true
|
|
||||||
},
|
},
|
||||||
dateFrom: {
|
dateFrom: {
|
||||||
type: Date,
|
type: Date,
|
||||||
|
|
|
||||||
|
|
@ -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 { 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_timesheet_manage_adjustment_request } from "../../controllers/va/vaTimesheetAdjustment/vaManageTimesheetAdjustment.controller.js"
|
||||||
import { va_verify_email } from "../../controllers/va/vaAuth/vaVerifyEmail.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
|
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
|
// 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.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.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);
|
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
|
// timesheet adjustment routes
|
||||||
|
|
||||||
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)
|
// Example: GET /va/search?name=john&email=@example.com&from=2025-01-01&to=2025-01-31 (NC)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue