354 lines
12 KiB
JavaScript
354 lines
12 KiB
JavaScript
import jwt from "jsonwebtoken";
|
|
import { VAModel, LeavesModel } from "../../../models/vaModel.js";
|
|
|
|
// Apply for leave
|
|
async function va_leave_apply_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;
|
|
const { dateFrom, dateTo, leaveType, comment } = req.body;
|
|
|
|
if (!dateFrom || !dateTo || !leaveType || !comment) {
|
|
return res.status(400).json({
|
|
success: false,
|
|
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"
|
|
});
|
|
}
|
|
|
|
const processedComment = processHTMLComment(comment);
|
|
// Create leave request
|
|
const leaveRequest = new LeavesModel({
|
|
vaId,
|
|
dateFrom: startDate,
|
|
dateTo: endDate,
|
|
days: totalDays.toString(),
|
|
leaveType,
|
|
comment: processedComment.processedHTML,
|
|
attachments: processedComment.extractedImages,
|
|
status: "PENDING"
|
|
});
|
|
|
|
await leaveRequest.save();
|
|
|
|
try {
|
|
// const salesforceUrl = `${process.env.SF_DOCTRACKER_API_URL}services/apexrest/docva/api/timesheet-adjustment`;
|
|
// const sfData = {
|
|
// "id": leaveRequest._id,
|
|
// "va": {
|
|
// "id": vaId
|
|
// },
|
|
// "dateFrom": startDate.toISOString().split('T')[0],
|
|
// "dateTo": endDate.toISOString().split('T')[0],
|
|
// "totalDays": totalDays,
|
|
// "leaveType": leaveType,
|
|
// "comment": processedComment.plainText, // Send plain text to Salesforce
|
|
// "attachments": processedComment.extractedImages, // Send extracted images info
|
|
// "status": "PENDING",
|
|
// "rejectionReason": ""
|
|
// }
|
|
|
|
// 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: "Leave request submitted successfully",
|
|
data: {
|
|
leaveId: leaveRequest._id,
|
|
dateFrom: startDate.toISOString().split('T')[0],
|
|
dateTo: endDate.toISOString().split('T')[0],
|
|
totalDays: totalDays,
|
|
leaveType,
|
|
comment: processedComment.processedHTML,
|
|
// attachments: processedComment.extractedImages,
|
|
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);
|
|
|
|
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
|
|
});
|
|
}
|
|
}
|
|
|
|
function processHTMLComment(htmlComment) {
|
|
try {
|
|
let processedHTML = htmlComment;
|
|
const extractedImages = [];
|
|
let plainText = '';
|
|
|
|
// Extract plain text from HTML for Salesforce
|
|
plainText = extractPlainTextFromHTML(htmlComment);
|
|
|
|
// Process base64 images in HTML
|
|
const imageProcessingResult = extractAndProcessBase64Images(htmlComment);
|
|
processedHTML = imageProcessingResult.processedHTML;
|
|
extractedImages.push(...imageProcessingResult.extractedImages);
|
|
|
|
return {
|
|
processedHTML,
|
|
plainText,
|
|
extractedImages
|
|
};
|
|
} catch (error) {
|
|
console.error("Error processing HTML comment:", error);
|
|
// Return original content if processing fails
|
|
return {
|
|
processedHTML: htmlComment,
|
|
plainText: extractPlainTextFromHTML(htmlComment),
|
|
extractedImages: []
|
|
};
|
|
}
|
|
}
|
|
|
|
// Helper function to extract plain text from HTML
|
|
function extractPlainTextFromHTML(html) {
|
|
try {
|
|
// Remove HTML tags and decode HTML entities
|
|
let plainText = html
|
|
.replace(/<[^>]*>/g, ' ') // Replace tags with spaces
|
|
.replace(/\s+/g, ' ') // Collapse multiple spaces
|
|
.trim();
|
|
|
|
// Decode common HTML entities
|
|
plainText = plainText
|
|
.replace(/ /g, ' ')
|
|
.replace(/&/g, '&')
|
|
.replace(/</g, '<')
|
|
.replace(/>/g, '>')
|
|
.replace(/"/g, '"')
|
|
.replace(/'/g, "'")
|
|
.replace(/'/g, "'")
|
|
.replace(///g, '/');
|
|
|
|
return plainText;
|
|
} catch (error) {
|
|
console.error("Error extracting plain text from HTML:", error);
|
|
return html.replace(/<[^>]*>/g, ' ').trim();
|
|
}
|
|
}
|
|
|
|
// Helper function to extract and process base64 images from HTML
|
|
function extractAndProcessBase64Images(html) {
|
|
const extractedImages = [];
|
|
let processedHTML = html;
|
|
|
|
try {
|
|
// Regex to find base64 images in HTML (various formats)
|
|
const base64Regex = /<img[^>]+src="data:image\/([a-zA-Z0-9+]+);base64,([^"]+)"[^>]*>/gi;
|
|
|
|
let match;
|
|
let imageIndex = 0;
|
|
|
|
while ((match = base64Regex.exec(html)) !== null) {
|
|
const fullMatch = match[0];
|
|
const imageType = match[1]; // png, jpeg, gif, etc.
|
|
const base64Data = match[2];
|
|
|
|
// Create a unique filename
|
|
const timestamp = Date.now();
|
|
const filename = `leave_image_${timestamp}_${imageIndex}.${imageType}`;
|
|
|
|
// Calculate approximate file size
|
|
const size = Math.floor((base64Data.length * 3) / 4); // Base64 size calculation
|
|
|
|
// Store image information
|
|
extractedImages.push({
|
|
filename: filename,
|
|
mimeType: `image/${imageType}`,
|
|
base64Data: base64Data,
|
|
size: size,
|
|
originalHTML: fullMatch, // Store original img tag for reference
|
|
index: imageIndex
|
|
});
|
|
|
|
// Replace base64 with a reference in HTML
|
|
const replacementHTML = `<img src="attachment://${filename}" alt="Leave evidence ${imageIndex + 1}" data-filename="${filename}" data-image-index="${imageIndex}" class="leave-image">`;
|
|
|
|
processedHTML = processedHTML.replace(fullMatch, replacementHTML);
|
|
|
|
imageIndex++;
|
|
}
|
|
|
|
return {
|
|
processedHTML,
|
|
extractedImages
|
|
};
|
|
} catch (error) {
|
|
console.error("Error processing base64 images:", error);
|
|
return {
|
|
processedHTML: html,
|
|
extractedImages: []
|
|
};
|
|
}
|
|
}
|
|
|
|
export { va_leave_apply_function }; |