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 = /]+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 = `Leave evidence ${imageIndex + 1}`; 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 };