diff --git a/backgroundWorker.js b/backgroundWorker.js index dca3f9362916c1ea93c1367b13bf564247512010..53c776bff0e038b9077182a18793cab0e12f480b 100644 --- a/backgroundWorker.js +++ b/backgroundWorker.js @@ -1,8 +1,8 @@ import axios from "axios"; - +import async from "async"; const targetURL = process.env.TARGET_URL || "https://hasura.upsmfac.org"; const notificationURL = process.env.REACT_APP_API_URL || "https://uphrh.in/api/api"; -const emailAuthToken = process.env.REACT_APP_AUTH_TOKEN || "Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJSR3RkMkZzeG1EMnJER3I4dkJHZ0N6MVhyalhZUzBSSyJ9.kMLn6177rvY53i0RAN3SPD5m3ctwaLb32pMYQ65nBdA"; +const emailAuthToken = process.env.REACT_APP_AUTH_TOKEN; const getBulkUploadAssessorSchedule = '/api/rest/getBulkUploadAssessorSchedule'; const filterAssessments = '/api/rest/filterAssessments'; @@ -16,6 +16,7 @@ const notify = '/email/notify'; const getFormSubmissionsByFormIds = '/api/rest/getFormSubmissionsByFormIds'; const updateStatusToBulkUpload = '/api/rest/updateStatusToBulkUpload'; const getAllAssessorsAPI = '/api/rest/getAllAssessors'; +const getAdminDetailsById = '/api/rest/getRegulator'; // Creating an Axios instance with custom headers const axiosInstance = axios.create({ @@ -28,41 +29,21 @@ const axiosInstance = axios.create({ }); const getBulkUpload = (processStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { - console.log(processStr); + //console.log(processStr); const response = await axiosInstance.post(targetURL + getBulkUploadAssessorSchedule, processStr); //console.log(response.data); resolve(response.data.assessor_schedule_bulk_upload); - // return response.data; } catch (error) { console.error('Error getBulkUpload:', error.message); throw error; } - }, 1000); }); }; -const getSchedule = (processStr) => { - return new Promise(resolve => { - setTimeout(async() => { - try { - - //console.log(processStr); - const response = await axiosInstance.post(targetURL + filterAssessments, processStr); - //console.log(response.data); - resolve(response.data.assessment_schedule); - //return response.data; - } catch (error) { - console.error('Error getSchedule:', error.message); - throw error; - } - }, 1000); - }); - }; + const getAllAssessors = () => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { const processStr = {"offsetNo":0,"limit": 100000} //console.log(processStr); @@ -71,35 +52,15 @@ const getSchedule = (processStr) => { const assessors = response.data.assessors; const users = assessors.filter(obj => obj["workingstatus"] == "Valid"); resolve(users); - //return response.data; } catch (error) { console.error('Error getUsersForScheduling:', error.message); throw error; } - }, 1000); }); }; - const getUsersForScheduling = (processStr) => { - return new Promise(resolve => { - setTimeout(async() => { - try { - - //console.log(processStr); - const response = await axiosInstance.post(targetURL + getUsersForSchedulingAssessment, processStr); - //console.log(response.data.assessors); - resolve(response.data.assessors); - //return response.data; - } catch (error) { - console.error('Error getUsersForScheduling:', error.message); - throw error; - } - }, 1000); - }); - }; const addAssessmentScheduleToDB = (scheduleStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { //console.log(scheduleStr); const response = await axiosInstance.post(targetURL + addAssessmentSchedule, scheduleStr); @@ -110,12 +71,10 @@ const addAssessmentScheduleToDB = (scheduleStr) => { console.error('Error addAssessmentScheduleToDB:', error.message); throw error; } - }, 1000); }); }; const addInstituteCourseToDB = (instituteStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { //console.log(instituteStr); const response = await axiosInstance.post(targetURL + addInstituteCourse, instituteStr); @@ -126,12 +85,10 @@ const addInstituteCourseToDB = (instituteStr) => { console.error('Error addInstituteCourse:', error.message); throw error; } - }, 1000); }); }; const addEventsToDB = (eventsStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { // console.log(eventsStr); const response = await axiosInstance.post(targetURL + addEvents, eventsStr); @@ -142,12 +99,10 @@ const addEventsToDB = (eventsStr) => { console.error('Error addEvents:', error.message); throw error; } - }, 1000); }); }; const updateFormToDB = (formStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { //console.log(formStr); const response = await axiosInstance.put(targetURL + updateForm, formStr); @@ -158,16 +113,24 @@ const updateFormToDB = (formStr) => { console.error('Error updateForm:', error.message); throw error; } - }, 1000); }); }; -const getApplicantDeviceIdFromDB = async (postData) => { - const res = await axiosInstance.post(targetURL + getApplicantDeviceId,postData); - return res; +const getAdminDetails = async (adminId) => { + try { + const postData = {"user_id":adminId}; + console.log(postData); + const res = await axiosInstance.post(targetURL + getAdminDetailsById , postData); + //console.log(res); + return res.data.regulator; + } catch (error) { + console.error('Error getAdminDetails:', error.message); + throw error; + } }; const sendEmailNotification = async (postData) => { + return new Promise(async(resolve) => { const res = await axiosInstance.post( notificationURL + notify, postData, @@ -178,11 +141,11 @@ const sendEmailNotification = async (postData) => { }, } ); - return res; + resolve( res); +}); }; const getFormSubmissionsByFormId = (arr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { const reqData = {"params": arr}; const response = await axiosInstance.post(targetURL+getFormSubmissionsByFormIds, reqData); @@ -191,13 +154,11 @@ const getFormSubmissionsByFormId = (arr) => { console.error('Error getFormSubmissionsByFormId:', error.message); throw error; } - }, 1000); }); }; const updateStatusToBulkUploadData = (obj) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { const response = await axiosInstance.post(targetURL + updateStatusToBulkUpload, obj); resolve(response.data); @@ -205,7 +166,6 @@ const updateStatusToBulkUploadData = (obj) => { console.error('Error updateStatusToBulkUploadData :', error.message); throw error; } - }, 1000); }); }; @@ -237,99 +197,125 @@ function getDatesFromTodayTo90Days() { return datesArray; } +function createEmailTable(emailTableRows, id, process_id, application_id, form_title, + application_type, course_type, assessor_id, uploaded_date, updateStatus, updateRemarks){ + if(emailTableRows.length()===1){ + emailTableRows.append("<tr><td>Id</td><td>Process_Id</td><td>Form_Id</td><td>Form_Title</td><td>Application_Type</td><td>Course_Type</td><td>Assessor_Id</td><td>Uploaded_Date</td><td>Status</td><td>Remarks</td></tr>"); + } + emailTableRows.append("<tr><td>"+id); + emailTableRows.append("</td> <td>" + process_id ); + emailTableRows.append("</td> <td>" + application_id); + emailTableRows.append("</td> <td>" + form_title ); + emailTableRows.append("</td> <td>" + application_type); + emailTableRows.append("</td> <td>" + course_type ); + emailTableRows.append("</td> <td>" + assessor_id ); + emailTableRows.append("</td> <td>" + uploaded_date ); + emailTableRows.append("</td> <td>" + updateStatus ); + emailTableRows.append("</td> <td>" + updateRemarks +"</td></tr>"); + return emailTableRows; +} +class StringBuffer { + constructor() { + this.buffer = []; + } -const performBackgroundTask = async () => { - const emailTemplate = "<!DOCTYPE html><html><head><meta charset=\'utf-8\'><title>Your Email Title</title><link href=\'https://fonts.googleapis.com/css2?family=Mulish:wght@400;600&display=swap\' rel=\'stylesheet\'></head>"+ - "<body style=\'font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;\'>"+ - "<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'>"+ - "<tr><td style=\'padding: 20px; text-align: center; background-color: #F5F5F5;\'><img src=\'https://regulator.upsmfac.org/images/upsmf.png\' alt=\'Logo\' style=\'max-width: 360px;\'></td></tr></table>"+ - "<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'><tr><td style=\'padding: 36px;\'><p style=\'color: #555555; font-size: 18px; font-family: \'Mulish\', Arial, sans-serif;\'>Dear ${applicantName},</p>"+ - "<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>We hope this email finds you well. We are glad to inform you that your application has been processed and was found fit for our next step which is on-ground assessment. "+ - "On-ground assessment for your application have been scheduled. Please expect us to visit your institute very soon.</p><p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>Following information will help you prepare for the scheduled on-ground assessment:\\n "+ - "<br/>1. A team of assessors will visit your institute for on-ground assessment. To make this process fair and transparent, institutes are not supposed to know the date of on-ground assessment and assessors are not supposed to know the institute they will be assessing till the day of assessment.\\n "+ - "<br/>2. We expect your institute open and accessible to our on-ground assessment team on any working day.\\n "+ - "<br/>3. Once on-ground assessment team prove their identity, they should be allowed to enter the institute and given full cooperation to carry out the on-ground assessment.</p>"+ - "<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>If you have any questions or need further clarification regarding the resubmission process, "+ - "please do not hesitate to reach out to our support executives at <Contact Details>. We are here to assist you and provide any necessary guidance.</p>"+ - "<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>Thank you for your time and continued interest in getting affiliated from our organization.</p></td></tr></table></body></html>"; + append(str) { + this.buffer.push(str); + return this; // Returning the instance for method chaining + } + toString() { + return this.buffer.join(''); + } + length() { + return this.buffer.length; + } +} +const getSchedule = async(processStr, callback) => { + //return new Promise(async(resolve) => { + try { + const response = await axiosInstance.post(targetURL + filterAssessments, processStr); + // console.log(response.data.assessment_schedule); + var formSchedules = response.data.assessment_schedule; + //resolve (formSchedules); + callback(null,formSchedules); + } catch (error) { + console.error('Error getSchedule:', error.message); + throw error; + } + // }); + }; + const getScheduless = async(processStr, callback) => { + //return new Promise(async(resolve) => { + try { + const response = await axiosInstance.post(targetURL + filterAssessments, processStr); + // console.log(response.data.assessment_schedule); + var formSchedules = response.data.assessment_schedule; + //resolve (formSchedules); + callback(null,formSchedules); + } catch (error) { + console.error('Error getSchedules:', error.message); + throw error; + } + // }); + }; +const addAssessorSchedule = (formSubmissionObj, element, assessorObj, callback)=>{ try { - const dateRange = getDatesFromTodayTo90Days(); - const activeAssessors = await getAllAssessors(); - - var bulkUploadData = await getBulkUpload({"where" : {"status": {"_eq": "Pending"}}}); - - // Find duplicates based on the "application_id" attribute - const duplicates = findDuplicateRecords(bulkUploadData, 'application_id','process_id'); - duplicates.forEach(async element => { - var updateStatus = "Failed"; - var updateRemarks = "Duplicate Record"; - - const updateData = await updateStatusToBulkUploadData({id:element.id,status:updateStatus,remarks:updateRemarks}); - bulkUploadData = bulkUploadData.filter(obj => (obj["application_id"] !== element.application_id && obj["id"]==element.id)); - }); - - const formIdArr = bulkUploadData.filter(obj => obj.hasOwnProperty("application_id")).map(obj => obj["application_id"]); - const formSubData = await getFormSubmissionsByFormId(formIdArr); - - var updateStatus = "Success"; - var updateRemarks = ""; - - bulkUploadData.forEach(async element => { - if(formSubData.length == 0){ - updateRemarks = "Form Application id is not existing in system or payment is not complete."; - updateStatus = "Failed"; - } else { - const formSubmissionObj = formSubData.filter(obj => obj["form_id"] === element.application_id); - const assessorObj = activeAssessors.filter(assessor => assessor["code"] === (element.assessor_id)+""); - if(assessorObj.length == 0){ - updateRemarks = "Assessor code is not existing in system."; - updateStatus = "Failed"; - } else { - //get existing form application schedules - const scheduleCriteria1 = {"offsetNo":0,"limit": 100000,"condition": {"applicant_form_id": {"_eq": element.application_id}}}; - const formSchedules = await getSchedule(scheduleCriteria1); - if(formSchedules.length == 0){ - //get existing assessor schedules - var datesNotInArray = []; - const scheduleCriteria = {"offsetNo":0,"limit": 100000,"condition": {"assessor_code": {"_eq": element.assessor_id+""}}}; - const schedules = await getSchedule(scheduleCriteria); - - //if no existing schedules, pick the nearest one - if(schedules.length == 0){ - datesNotInArray = dateRange; - }else{ - const scheduledDates = schedules.filter(obj => obj.hasOwnProperty("date")).map(obj => obj["date"]); - datesNotInArray = dateRange.filter(date => !scheduledDates.includes(date)); - } - const nearestUpcomingFreeDate = datesNotInArray[0]; - + //get existing assessor schedules + var datesNotInArray = []; + const scheduleCriteria = {"offsetNo":0,"limit": 100000,"condition": {"assessor_code": {"_eq": element.assessor_id+""}}}; + getScheduless(scheduleCriteria,(error,schedules)=> { + if(error){ + console.error(`Error getScheduless ${element.id}:`, error); + throw error; + }else{ + const dateRange = getDatesFromTodayTo90Days(); + //if no existing schedules, pick the nearest one + if(schedules.length == 0){ + datesNotInArray = dateRange; + }else{ + const scheduledDates = schedules.filter(obj => obj.hasOwnProperty("date")).map(obj => obj["date"]); + datesNotInArray = dateRange.filter(date => !scheduledDates.includes(date)); + const nearestUpcomingFreeDate = datesNotInArray[0]; + async.waterfall([ + function addSchedul(callback1){ //assign new schedule const scheduleStr ={"assessment_schedule":[{ "assessor_code":assessorObj[0].code, - "date":nearestUpcomingFreeDate,//nextDate.toLocaleString(), + "date":nearestUpcomingFreeDate, "institute_id":formSubmissionObj[0].institute.id, "applicant_form_id":formSubmissionObj[0].form_id}]}; - const addedRec = await addAssessmentScheduleToDB(scheduleStr); - //scheduledDates.push(nearestUpcomingFreeDate); - + const addedRec = addAssessmentScheduleToDB(scheduleStr); + console.log("added schedule"); + callback1(null,addedRec); + }, + function addinst(addedRec, callback2){ const instituteStr ={"institute_course":[{ "institute_id":formSubmissionObj[0].institute.id, "institute_type":"[{\"courseType\":\""+formSubmissionObj[0].course_type+"\",\"courseLevel\":\""+formSubmissionObj[0].course_level+"\"}]"}], "institute_form":[{"course_id":formSubmissionObj[0].course_id,"applicant_form_id":formSubmissionObj[0].form_id,"institute_id":formSubmissionObj[0].institute.id,"assessment_date":nearestUpcomingFreeDate}]}; const addInstRec = addInstituteCourseToDB(instituteStr); - + console.log("added Institute"); + callback2(null,addInstRec); + }, + function addEve(addInstRec, callback3){ const eventsStr = {"events":[{ "created_date":new Date().toLocaleString(), "entity_id":formSubmissionObj[0].form_id+"", "entity_type":"form", "event_name":"Inspection Scheduled", "remarks":"Round 1 inspection scheduled"}]}; - const addeventsRec = await addEventsToDB(eventsStr); - + const addeventsRec = addEventsToDB(eventsStr); + console.log("Added event"); + callback3(null,addeventsRec); + }, + function updateFormSub(addeventsRec,callback4){ const formStr = {"form_id":formSubmissionObj[0].form_id,"form_status":"Inspection Scheduled"}; - const addFormRec = await updateFormToDB(formStr); - + const addFormRec = updateFormToDB(formStr); + console.log("Updated From Sub"); + callback4(null,addFormRec); + }, + function sendMail(addFormRec, callback5){ var email = emailTemplate.replace("${applicantName}",formSubmissionObj[0].institute.name); const emailData = { "recipientEmail":[formSubmissionObj[0].institute.email], @@ -337,22 +323,246 @@ const performBackgroundTask = async () => { "emailBody":email }; sendEmailNotification(emailData); - console.log('Notification sent..'); - updateRemarks = "Updated Successfully."; - updateStatus = "Success"; + console.log('Applicant notification sent'); + callback5(null,"Success"); + }, + function returningCallback(status){ + callback(null, true, status, "Updated Successfully."); + } + ], (error, finalResult) => { + if (error) { + console.error(`Error for element ${element.id}:`, error); + } else { + console.log(`Final Result for element ${element.id}:`, finalResult); + } + }); + } + } + } + ); +} catch (error) { + console.error('Error addAssessorSchedule:', error.message); + throw error; +} + +} +const executeBulkUpload = (bulkUploadData, emailTableRows, adminId, formSubData, activeAssessors, callback1) => { + var updateStatus = ""; + var updateRemarks = ""; + console.log("step 5.1 - "+bulkUploadData.length +" adminId "+adminId); + var itemsProcessed = 0; + var assessorObj; + if(bulkUploadData.length === 0){ + callback1(null,"success",emailTableRows, adminId); + }else{ + bulkUploadData.forEach((element) => { + async.waterfall([ + function checkAssessor(callback2){ + var isUpdate = false; + assessorObj = activeAssessors.filter(assessor => assessor["code"] === (element.assessor_id)+""); + if(assessorObj.length === 0){ + updateRemarks = "Assessor code is not existing in system."; + updateStatus = "Failed"; + callback2(null, true, updateStatus, updateRemarks); + }else{ + callback2(null, false, "", ""); + } + }, + function getSchedul(isUpdate, updateStatus, updateRemarks, callback3) { + if(!isUpdate){ + //get existing form application schedule + const scheduleCriteria1 = {"offsetNo":0,"limit": 100000,"condition": {"applicant_form_id": {"_eq": element.application_id}}}; + getSchedule(scheduleCriteria1, (error,formSchedules)=> { + if(error){ + console.error(`Error getSchedule ${element.id}:`, error); + throw error; + }else{ + if(formSchedules.length === 0){ + callback3(null, false, "", ""); + }else{ + updateRemarks = "Form Schedule Already Exists."; + updateStatus = "Failed"; + callback3(null, true, updateStatus, updateRemarks); + } + + }}); }else{ - updateRemarks = "Form Schedule Already Exists."; - updateStatus = "Failed"; + callback3(null, isUpdate, updateStatus, updateRemarks); + } + }, + function addRecords(isUpdate, updateStatus, updateRemarks, callback41){ + if(!isUpdate){ + var formSubmissionObj = formSubData.filter(obj => obj["form_id"] === element.application_id); + addAssessorSchedule(formSubmissionObj, element, assessorObj,(error,isUpdate, updateStatus,updateRemarks)=>{ + if(error){ + console.error(`Error addAssessorSchedule ${element.id}:`, error); + throw error; + }else{ + callback41(null, isUpdate, updateStatus, updateRemarks); + } + }); + + }else{ + callback41(null, isUpdate, updateStatus, updateRemarks); } + }, + function updateDB1(isUpdate, updateStatus, updateRemarks){ + const updateData = updateStatusToBulkUploadData({id:element.id,status:updateStatus,remarks:updateRemarks}); + emailTableRows.append(createEmailTable(emailTableRows, element.id, element.process_id, element.application_id, element.form_title, + element.application_type, element.course_type, element.assessor_id, element.uploaded_date, updateStatus, updateRemarks)); + console.log("Updated id :" + element.id); + adminId = element.uploaded_by; + itemsProcessed++; + if(itemsProcessed === bulkUploadData.length){ + callback1(null,"success",emailTableRows, adminId); + } + } + ], (error, finalResult) => { + if (error) { + console.error(`Error for element ${element.id}:`, error); + } else { + console.log(`Final Result for element ${element.id}:`, finalResult); + } + }); + }); + } +}; + +const findMissingRecords = (object1, object2, key1,key2) => { + const set1 = new Set(object1.map(item1 => item1[key1])); + const missingRecords = object2.filter(item2 => !set1.has(item2[key2])); + return missingRecords; +}; +const removeDuplicatesFromBulkUpload = (bulkUploadData, emailTableRows, callback) =>{ + // Find duplicates based on the "application_id" attribute + const duplicates = findDuplicateRecords(bulkUploadData, 'application_id','process_id'); + var itemsProcessed = 0; + var adminId = ""; + console.log("duplicates "+ duplicates.length+" : "+bulkUploadData.length); + if(duplicates.length === 0){ + callback(null,"success",bulkUploadData,emailTableRows,adminId); + }else{ + duplicates.forEach(element => { + async.waterfall([ + function updatedbrec(callback1){ + var updateStatus = "Failed"; + var updateRemarks = "Duplicate Record"; + const updateData = updateStatusToBulkUploadData({id:element.id,status:updateStatus,remarks:updateRemarks}); + callback1(null, updateStatus, updateRemarks); + }, + function removDup( updateStatus, updateRemarks){ + emailTableRows.append(createEmailTable(emailTableRows, element.id, element.process_id, element.application_id, element.form_title, + element.application_type, element.course_type, element.assessor_id, element.uploaded_date, updateStatus, updateRemarks)); + + bulkUploadData = bulkUploadData.filter(obj => (obj["id"] !== element.id)); + adminId = element.uploaded_by; + itemsProcessed++; + if(itemsProcessed === duplicates.length) { + callback(null,"success",bulkUploadData,emailTableRows,adminId); + } + }]); + }); + } +}; + +const removeInvalidFormSubmissionIds = async (bulkUploadData, emailTableRows, adminId, formSubData, callback) =>{ + var itemsProcessed = 0; + const missingRecords = findMissingRecords( formSubData, bulkUploadData,"form_id", "application_id"); + console.log("missingRecords "+ missingRecords.length+" : "+bulkUploadData.length+":"+formSubData.length); + if(missingRecords.length === 0){ + callback(null,"success",bulkUploadData,emailTableRows,adminId); + } else { + missingRecords.forEach(async (element) => { + async.waterfall([ + function updatedbrec(callback1){ + var updateRemarks = "Form Application id is not existing in system or payment is not complete."; + var updateStatus = "Failed"; + const updateData = updateStatusToBulkUploadData({id:element.id,status:updateStatus,remarks:updateRemarks}); + callback1(null, updateStatus, updateRemarks); + }, + function removDup( updateStatus, updateRemarks){ + emailTableRows.append(createEmailTable(emailTableRows, element.id, element.process_id, element.application_id, element.form_title, + element.application_type, element.course_type, element.assessor_id, element.uploaded_date, updateStatus, updateRemarks)); + bulkUploadData = bulkUploadData.filter(obj => ( obj["id"]!==element.id)); + adminId = element.uploaded_by; + itemsProcessed++; + if(itemsProcessed === missingRecords.length) { + callback(null,"success",bulkUploadData,emailTableRows,adminId); } - } - const updateData = await updateStatusToBulkUploadData({id:element.id,status:updateStatus,remarks:updateRemarks}); - + }]); + }); + } +}; +var emailTemplate = "<!DOCTYPE html><html><head><meta charset=\'utf-8\'><title>Your Email Title</title><link href=\'https://fonts.googleapis.com/css2?family=Mulish:wght@400;600&display=swap\' rel=\'stylesheet\'></head>"+ +"<body style=\'font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;\'>"+ +"<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'>"+ +"<tr><td style=\'padding: 20px; text-align: center; background-color: #F5F5F5;\'><img src=\'https://regulator.upsmfac.org/images/upsmf.png\' alt=\'Logo\' style=\'max-width: 360px;\'></td></tr></table>"+ +"<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'><tr><td style=\'padding: 36px;\'><p style=\'color: #555555; font-size: 18px; font-family: \'Mulish\', Arial, sans-serif;\'>Dear ${applicantName},</p>"+ +"<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>We hope this email finds you well. We are glad to inform you that your application has been processed and was found fit for our next step which is on-ground assessment. "+ +"On-ground assessment for your application have been scheduled. Please expect us to visit your institute very soon.</p><p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>Following information will help you prepare for the scheduled on-ground assessment:\\n "+ +"<br/>1. A team of assessors will visit your institute for on-ground assessment. To make this process fair and transparent, institutes are not supposed to know the date of on-ground assessment and assessors are not supposed to know the institute they will be assessing till the day of assessment.\\n "+ +"<br/>2. We expect your institute open and accessible to our on-ground assessment team on any working day.\\n "+ +"<br/>3. Once on-ground assessment team prove their identity, they should be allowed to enter the institute and given full cooperation to carry out the on-ground assessment.</p>"+ +"<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>If you have any questions or need further clarification regarding the resubmission process, "+ +"please do not hesitate to reach out to our support executives at <Contact Details>. We are here to assist you and provide any necessary guidance.</p>"+ +"<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>Thank you for your time and continued interest in getting affiliated from our organization.</p></td></tr></table></body></html>"; + +var adminEmailTemplate = "<!DOCTYPE html><html><head><meta charset=\'utf-8\'><title>Your Email Title</title><link href=\'https://fonts.googleapis.com/css2?family=Mulish:wght@400;600&display=swap\' rel=\'stylesheet\'></head>"+ +"<body style=\'font-family: Arial, sans-serif; background-color: #f4f4f4; margin: 0; padding: 0;\'>"+ +"<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'>"+ +"<tr><td style=\'padding: 20px; text-align: center; background-color: #F5F5F5;\'><img src=\'https://regulator.upsmfac.org/images/upsmf.png\' alt=\'Logo\' style=\'max-width: 360px;\'></td></tr></table>"+ +"<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'0\' cellspacing=\'0\' border=\'0\'><tr><td style=\'padding: 36px;\'><p style=\'color: #555555; font-size: 18px; font-family: \'Mulish\', Arial, sans-serif;\'>Dear ${adminName},</p>"+ +"<p style=\'color: #555555; font-size: 18px; line-height: 1.6; font-family: \'Mulish\', Arial, sans-serif;\'>Please find the assessor upload status below: "+ +"<table width=\'100%\' bgcolor=\'#ffffff\' cellpadding=\'5\' border=\'1\'>${emailTableRows}</table>"+ +"</p></td></tr></table></body></html>"; + +const performBackgroundTask = async () => { + + try { + const emailTableRows = new StringBuffer(); + emailTableRows.append("") + const activeAssessors = await getAllAssessors(); + var bulkUploadData = await getBulkUpload({"where" : {"status": {"_eq": "Pending"}}}); + removeDuplicatesFromBulkUpload(bulkUploadData, emailTableRows, async (error,result,bulkUploadData,emailTableRows,adminId)=>{ + if(error){ + console.error('Error removeDuplicatesFromBulkUpload:', error.message); + throw error; + }else{ + const formIdArr = bulkUploadData.filter(obj => obj.hasOwnProperty("application_id")).map(obj => obj["application_id"]); + const formSubData = await getFormSubmissionsByFormId(formIdArr); + + await removeInvalidFormSubmissionIds(bulkUploadData, emailTableRows, adminId, formSubData,(error,result,bulkUploadData,emailTableRows,adminId)=>{ + if(error){ + console.error('Error removeInvalidFormSubmissionIds:', error.message); + throw error; + }else{ + executeBulkUpload(bulkUploadData, emailTableRows,adminId, formSubData, activeAssessors, async (error,result,emailTableRows,adminId) => { + if(error){ + console.error('Error executeBulkUpload:', error.message); + throw error; + }else{ + if(adminId !==""){ + const adminDetails = await getAdminDetails(adminId); + var adminEmail = adminEmailTemplate.replace("${adminName}", adminDetails[0].full_name); + adminEmail = adminEmail.replace("${emailTableRows}", emailTableRows.toString()); + const adminEmailData = { + "recipientEmail":[adminDetails[0].email,"reshmi.nair@tarento.com"], + "emailSubject":"Assessor Upload Status", + "emailBody":adminEmail + }; + sendEmailNotification(adminEmailData); + console.log('Admin notification sent..'); + } + } + }); + } + }); + } }); // Process the API response - //console.log('API Response:', bulkUploadData); } catch (error) { console.error('Error making API call:', error.message); + throw error; } } diff --git a/index.js b/index.js index bbddd96dd0ad8136af004ad6cfb138a28527cfed..6d66070c08faa9596b82a8b12e6e97e9292dd79a 100644 --- a/index.js +++ b/index.js @@ -2,6 +2,7 @@ import express from "express"; import { createProxyMiddleware } from "http-proxy-middleware"; import endpoints from "./endpoints/endpoints.js"; import easyPay from "./utils/easypay.js"; +import scheduledJob from "./scheduler.js"; import axios from "axios"; const app = express(); @@ -13,7 +14,7 @@ import multer from "multer"; //const multer = require('multer'); import csv from "csv-parser"; //const csv = require('csv-parser'); -import fs from "fs"; +import exceljs from "exceljs"; //const fs = require('fs'); import stream from "stream"; @@ -136,94 +137,98 @@ app.post("/payment/v2/generatelink", async (req, res) => { } }); -// Parse CSV header -const parseHeader = (csvData) => { - const lines = csvData.split('\n'); - if (lines.length > 0) { - return lines[0].split(',').map(header => header.trim().toLowerCase()); - } - return []; -}; const bulkUploadAssessorSchedule = '/api/rest/bulkUploadAssessorSchedule'; const bulkUpload = (updateStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { console.log(updateStr); const response = await axiosInstance.post(targetURL+bulkUploadAssessorSchedule, updateStr); resolve(response.data.insert_assessor_schedule_bulk_upload.returning); } catch (error) { - console.error('Error updating status:', error.message); + console.error('Error in updating data to bulk upload table:', error.message); throw error; } - }, 1000); }); }; -// Endpoint for uploading CSV file -app.post('/upload/assessor/schedule', upload.single('csvFile'), (req, res) => { +// Endpoint for uploading file +app.post('/upload/assessor/schedule', upload.single('file'), async(req, res) => { + if (!req.file) { + return res.status(400).json({ error: 'No file uploaded' }); + } + if (!req.body.userId) { + return res.status(400).json({ error: 'UserId is not provided' }); + } try { - //const validCSVHeaders = [form_id,form_title,aplication_type,course_type,assessor_id]; - - // Get CSV data from the uploaded file - const csvDataString = req.file.buffer.toString('utf8'); + const processId = uuidv4(); - // Get CSV header - const csvHeader = parseHeader(csvDataString); - //console.log(csvHeader) + const validfileHeaders = ["form_id","form_title","application_type","course_type","assessor_id"]; + const { userId } = req.body; + + // Get data from the uploaded file + const fileDataString = req.file.buffer; + const readableStream = stream.Readable.from(fileDataString); - const readableStream = stream.Readable.from(csvDataString); - const processId = uuidv4(); - // Parse CSV data + const workbook = new exceljs.Workbook(); + await workbook.xlsx.read(readableStream); + const worksheet = workbook.getWorksheet(1); // Assuming the first sheet + const results = []; - const parseOptions = { - mapHeaders: ({ header, index }) => header.toLowerCase().trim(), // Normalize header names - mapValues: ({ value }) => value.trim(), // Trim whitespace from values - }; - //console.log(parseOptions) - //const response = {}; - //DISTRICT,PARENT CENTER CODE,CHILD CENTER CODE,INSTITUTE NAME,ASSESSMENT DATE,ASSESSOR IDS,STATUS - //find application id using institute name - readableStream.pipe(csv(parseOptions)) - .on('data', (data) => { - const formid = /\d/.test(data.form_id)==true?parseInt(data.form_id, 10):0; - const assessor_code = /\d/.test(data.assessor_id)==true?parseInt(data.assessor_id, 10):0; - if(formid != 0 && assessor_code!= 0 ){ - const newObj = { - "application_id" : formid, - "form_title" : data.form_title, - "application_type": data.application_type, - "course_type" : data.course_type, - "assessor_id" : assessor_code - }; - results.push(newObj); + const header = []; + // Iterate over rows and columns + worksheet.eachRow((row, rowNumber) => { + const rowData = {}; + row.eachCell((cell, colNumber) => { + // Assuming the first row contains headers + if (rowNumber === 1) { + header [colNumber]= cell.value.trim().toLowerCase().replace(/ /g, '_'); + } else { + if(validfileHeaders.includes(header [colNumber])) { + var cellValue; + if(header [colNumber] == "form_id"){ + cellValue = /\d/.test(cell.value)==true?parseInt(cell.value, 10):0; + rowData["application_id"] = cellValue; + }else if(header [colNumber] == "assessor_id") { + cellValue = /\d/.test(cell.value)==true?parseInt(cell.value, 10):0; + rowData[header [colNumber]] = cellValue; + }else{ + cellValue = cell.value; + rowData[header [colNumber]] = cellValue; + } } - }) - .on('end', async () => { - results.forEach((item) => { - item["process_id"]=processId; - item["uploaded_by"]="system"; - item["status"]="Pending"; - item["uploaded_date"]=new Date().toLocaleString(); - }); - //{"assessor_schedule_bulk_upload": [{"id": 1,"process_id":1,"uploaded_by":"system","uploaded_date": "2023-11-17","application_id":234,"assessor_id":232}]} - const bulkUploadDetails = bulkUpload({"assessor_schedule_bulk_upload":results}); - - const worker = new Worker('./backgroundWorker.js'); - worker.on('message', (message) => { - console.log(`Background Worker Message: ${message}`); - }); - worker.on('error', (error) => { - console.error(`Background Worker Error: ${error}`); - }); - worker.on('exit', (code) => { - console.log(`Background Worker exited with code ${code}`); - }); - - // Process the parsed CSV data - res.json({ data: results }); - + } }); + if(Object.keys(rowData).length !== 0){ + if(rowData.assessor_id == undefined){ + rowData["assessor_id"]=0; + } + results.push(rowData); + } + }); + results.forEach((item) => { + item["process_id"]=processId; + item["uploaded_by"]=userId; + item["status"]="Pending"; + item["uploaded_date"]=new Date().toLocaleString(); + }); + //{"assessor_schedule_bulk_upload": [{"id": 1,"process_id":1,"uploaded_by":"system","uploaded_date": "2023-11-17","application_id":234,"assessor_id":232}]} + const bulkUploadDetails = bulkUpload({"assessor_schedule_bulk_upload":results}); + + const worker = new Worker('./backgroundWorker.js'); + worker.on('message', (message) => { + console.log(`Background Worker Message: ${message}`); + }); + worker.on('error', (error) => { + console.error(`Background Worker Error: ${error}`); + }); + worker.on('exit', (code) => { + console.log(`Background Worker exited with code ${code}`); + }); + + // Process the parsed CSV data + res.json({ data: results }); + + } catch (error) { console.error("Error:", error); console.error("Error message:", error.message); @@ -231,7 +236,28 @@ app.post('/upload/assessor/schedule', upload.single('csvFile'), (req, res) => { } }); +// Custom Stream class to create a readable stream from a buffer +class BufferStream { + constructor(buffer) { + this.buffer = buffer; + this.position = 0; + } + + read(size) { + if (this.position >= this.buffer.length) { + return null; + } + + const chunk = this.buffer.slice(this.position, this.position + size); + this.position += chunk.length; + + return chunk; + } +} app.listen(port, () => { console.log(`Server is listening on port ${port}`); }); + + + diff --git a/package-lock.json b/package-lock.json index ae1c4cf0a0254844b397780d41ec1da8145355c1..99816d9672a5adf29ba103992b3fad5c299d7753 100644 --- a/package-lock.json +++ b/package-lock.json @@ -827,4 +827,4 @@ } } } -} +} \ No newline at end of file diff --git a/package.json b/package.json index 5426fffe637d6c5eb6b5016ca2946f80c2de9929..fa7b37379da3dfe3fe941132d53d2c0b51c36945 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "dependencies": { "axios": "^1.4.0", "csv-parser": "^3.0.0", + "exceljs": "^4.4.0", "express": "^4.18.2", "http-proxy-middleware": "^2.0.6", "multer": "^1.4.5-lts.1", diff --git a/scheduler.js b/scheduler.js index 6d62e691f2d37774004a7a640704c43b1adb2453..3c7919548cbcd5816523bdb531517402f18ec56d 100644 --- a/scheduler.js +++ b/scheduler.js @@ -21,9 +21,7 @@ const axiosInstance = axios.create({ //Get threshold days from DB const fetchThreshold = () => { - - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { const reqData = {"searchString" : {"status": {"_eq": true}, "type": {"_eq": "scheduler"}},"offSet":0,"limit": 100}; const response = await axiosInstance.post(targetURL+GET_THRESHOLD_DAYS,reqData); @@ -32,15 +30,13 @@ const fetchThreshold = () => { console.error('Error fetching threshold days:', error.message); throw error; } - }, 1000); }); }; //Get submitted date and reviewed date from DB const fetchData = (arr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { const reqData = {"params": arr}; const response = await axiosInstance.post(targetURL+GET_FORM_SUBMISSIONS_BY_STATUS, reqData); @@ -49,14 +45,12 @@ const fetchData = (arr) => { console.error('Error fetching application data:', error.message); throw error; } - }, 1000); }); }; // Update status using the API const updateStatus = (updateStr) => { - return new Promise(resolve => { - setTimeout(async() => { + return new Promise(async(resolve) => { try { console.log(updateStr); const response = await axiosInstance.post(targetURL+UPDATE_ENDPOINT, updateStr); @@ -65,7 +59,6 @@ const updateStatus = (updateStr) => { console.error('Error updating status:', error.message); throw error; } - }, 1000); }); }; @@ -84,7 +77,8 @@ const updateStatus = (updateStr) => { }; // Schedule the task to run every day at midnight -cron.schedule('0 0 * * *', async () => { +const scheduledJob = cron.schedule('0 0 * * *', async () => { + console.log('Cron job running at 12 AM'); try { const currentDate = new Date(); const thresholdDaysMap = await fetchThreshold(); @@ -117,7 +111,7 @@ cron.schedule('0 0 * * *', async () => { emailBody: email, }; - sendEmailNotification(emailData); + await sendEmailNotification(emailData); console.log('Notification sent..'); }); } @@ -126,6 +120,12 @@ cron.schedule('0 0 * * *', async () => { } catch (error) { console.error('Error updating status:', error.message); } +}, { + //scheduled: true, + timezone: 'Asia/Kolkata', }); -console.log('Scheduler started. Waiting for scheduled tasks...'); \ No newline at end of file +console.log('Scheduler started. Waiting for scheduled tasks...'); +export default { + scheduledJob +}; \ No newline at end of file