diff --git a/app/services/scheduler.service.js b/app/services/scheduler.service.js new file mode 100644 index 0000000..00e5ee2 --- /dev/null +++ b/app/services/scheduler.service.js @@ -0,0 +1,243 @@ +const cron = require('node-cron'); +const mysql = require('mysql2/promise'); +const SubmissionAutoFillService = require('./auto_fill_missing_quarterly_submissions_service'); // Adjust path +const IPICalculationService = require('./ipi_calculation_service'); + +// Database configuration +const dbConfig = { + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASSWORD, + database: process.env.DB_NAME, + dialect: process.env.DB_DIALECT || 'mysql', + pool: { + max: 5, + min: 0, + acquire: 30000, + idle: 10000 + } +}; + + + +class AutomatedSchedulerService { + + + constructor() { + this.isRunning = false; + this.pool = mysql.createPool(dbConfig); + } + + /** + * Fetch the latest entry from quarterly_windows_configuration_master table + */ + async getLatestQuarterlyWindow() { + const connection = await this.pool.getConnection(); + try { + const query = ` + SELECT + year, + quarter, + end_date, + start_date + FROM quarterly_windows_configuration_master + ORDER BY year DESC, + CASE quarter + WHEN 'Q1' THEN 1 + WHEN 'Q2' THEN 2 + WHEN 'Q3' THEN 3 + WHEN 'Q4' THEN 4 + END DESC + LIMIT 1 + `; + + // const query = ` + // SELECT + // year, + // quarter, + // end_date, + // start_date + // FROM quarterly_windows_configuration_master + // ORDER BY id DESC + // LIMIT 1 + // `; + + const [rows] = await connection.query(query); + return rows[0] || null; + } finally { + connection.release(); + } + } + + /** + * Execute survey auto submit logic + */ + async executeSurveyAutoSubmit(year, quarter) { + console.log(`[${new Date().toISOString()}] Starting survey_auto_submit for ${year} ${quarter}`); + + try { + const autoSubmissionService = new SubmissionAutoFillService(dbConfig); + + // Auto-fill missing submissions for specific year and quarter + const result = await autoSubmissionService.autoFillMissingSubmissions(year, quarter); + + // Verify completeness for specific quarter + const result3 = await autoSubmissionService.verifyCompleteness(year, quarter); + + console.log(`[${new Date().toISOString()}] survey_auto_submit completed successfully`); + + return { + success: true, + autoFillMissingSubmissions: result, + verifyCompleteness: result3 + }; + } catch (error) { + console.error(`[${new Date().toISOString()}] Error in survey_auto_submit:`, error); + throw error; + } + } + + /** + * Execute calculate quarter logic + */ + async executeCalculateQuarter(year, quarter) { + console.log(`[${new Date().toISOString()}] Starting calculate_quarter for ${year} ${quarter}`); + + try { + // Quarter to month mapping + const quarterMap = { + Q1: { startMonth: 1, endMonth: 3 }, + Q2: { startMonth: 4, endMonth: 6 }, + Q3: { startMonth: 7, endMonth: 9 }, + Q4: { startMonth: 10, endMonth: 12 }, + }; + + const selectedQuarter = quarterMap[quarter]; + + if (!selectedQuarter) { + throw new Error('Invalid quarter. Accepted values: Q1, Q2, Q3, Q4'); + } + + const { startMonth, endMonth } = selectedQuarter; + const service = new IPICalculationService(dbConfig); + const results = []; + + for (let month = startMonth; month <= endMonth; month++) { + const result = await service.runCompleteCalculation(year, month); + results.push({ + month, + ...result + }); + } + + await service.close(); + + console.log(`[${new Date().toISOString()}] calculate_quarter completed successfully`); + + return { + success: true, + message: `IPI calculated for ${year} months ${startMonth}-${endMonth}`, + data: results + }; + } catch (error) { + console.error(`[${new Date().toISOString()}] Error in calculate_quarter:`, error); + throw error; + } + } + + /** + * Main scheduler logic - runs daily + */ + async runScheduledTask() { + // Prevent concurrent execution + if (this.isRunning) { + console.log(`[${new Date().toISOString()}] Scheduler already running, skipping this execution`); + return; + } + + this.isRunning = true; + + try { + console.log(`[${new Date().toISOString()}] ========================================`); + console.log(`[${new Date().toISOString()}] Starting scheduled task execution`); + + // Step 1: Get latest quarterly window configuration + const latestWindow = await this.getLatestQuarterlyWindow(); + + if (!latestWindow) { + console.log(`[${new Date().toISOString()}] No quarterly window configuration found`); + return; + } + + console.log(`[${new Date().toISOString()}] Latest window: Year=${latestWindow.year}, Quarter=${latestWindow.quarter}, End Date=${latestWindow.end_date}`); + + // Step 2: Check if current date is greater than end_date + const currentDate = new Date(); + const endDate = new Date(latestWindow.end_date); + + console.log(`[${new Date().toISOString()}] Current Date: ${currentDate.toISOString()}`); + console.log(`[${new Date().toISOString()}] End Date: ${endDate.toISOString()}`); + + if (currentDate < endDate) { + console.log(`[${new Date().toISOString()}] Current date is not greater than end_date. Skipping execution.`); + return; + } + + console.log(`[${new Date().toISOString()}] Current date is greater than end_date. Proceeding with execution.`); + + // Step 3: Execute survey_auto_submit + const surveyResult = await this.executeSurveyAutoSubmit( + latestWindow.year, + latestWindow.quarter + ); + + // Step 4: Execute calculate_quarter after survey_auto_submit completes + const calculationResult = await this.executeCalculateQuarter( + latestWindow.year, + latestWindow.quarter + ); + + console.log(`[${new Date().toISOString()}] ========================================`); + console.log(`[${new Date().toISOString()}] All scheduled tasks completed successfully`); + console.log(`[${new Date().toISOString()}] Survey Result:`, JSON.stringify(surveyResult, null, 2)); + console.log(`[${new Date().toISOString()}] Calculation Result:`, JSON.stringify(calculationResult, null, 2)); + + } catch (error) { + console.error(`[${new Date().toISOString()}] Error in scheduled task:`, error); + // You might want to send an alert/notification here + } finally { + this.isRunning = false; + } + } + + /** + * Start the scheduler + * Runs every day at 2:00 AM (you can adjust the time) + */ + start() { + console.log(`[${new Date().toISOString()}] Starting automated scheduler...`); + + // Run every day at 2:00 AM + // Format: minute hour day month weekday + // '0 2 * * *' = At 2:00 AM every day + cron.schedule('0 2 * * *', () => { + this.runScheduledTask(); + }); + + console.log(`[${new Date().toISOString()}] Scheduler started successfully. Will run daily at 2:00 AM.`); + + // Optional: Run immediately on startup for testing + // Uncomment the line below if you want to test immediately + // this.runScheduledTask(); + } + + /** + * Manual trigger for testing + */ + async manualTrigger() { + console.log(`[${new Date().toISOString()}] Manual trigger initiated...`); + await this.runScheduledTask(); + } +} + +module.exports = AutomatedSchedulerService; diff --git a/package-lock.json b/package-lock.json index 23da90b..91e5587 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "morgan": "^1.10.1", "multer": "^2.0.2", "mysql2": "^3.15.2", + "node-cron": "^4.2.1", "nodemailer": "^7.0.10", "nodemon": "^3.1.10", "sanitize-html": "^2.17.0", @@ -2237,6 +2238,14 @@ "node": ">= 0.6" } }, + "node_modules/node-cron": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", + "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nodemailer": { "version": "7.0.10", "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", diff --git a/package.json b/package.json index 43ebd03..f845eed 100644 --- a/package.json +++ b/package.json @@ -26,6 +26,7 @@ "morgan": "^1.10.1", "multer": "^2.0.2", "mysql2": "^3.15.2", + "node-cron": "^4.2.1", "nodemailer": "^7.0.10", "nodemon": "^3.1.10", "sanitize-html": "^2.17.0", diff --git a/server.js b/server.js index 4e016c7..ccb5a46 100644 --- a/server.js +++ b/server.js @@ -12,6 +12,8 @@ const sanitizeInput = require("./app/utils/sanitizeInput"); const verifySignature = require("./app/middleware/app.middleware"); const authController = require("./app/controllers/auth.controller"); const establishmentController = require("./app/controllers/establishment.controller"); +const AutomatedSchedulerService = require('./app/services/scheduler.service'); + require("dotenv").config(); const csrf = require("csurf"); @@ -297,6 +299,27 @@ app.get("/api/test", (req, res) => { const deploymentController = require("./app/controllers/deployment.controller"); app.post("/deploy", deploymentController.deployment); + +/** + * ========================= + * Auto Submission and iip calculation Automation + * ========================= + */ +const scheduler = new AutomatedSchedulerService(); +scheduler.start(); +// manual trigger endpoint +app.post('/api/admin/trigger-scheduler', async (req, res) => { + try { + await scheduler.manualTrigger(); + res.json({ success: true, message: 'Scheduler triggered successfully' }); + } catch (error) { + res.status(500).json({ + success: false, + message: 'Error triggering scheduler', + error: error.message + }); + } +}); /** * ========================= * START SERVER @@ -305,4 +328,5 @@ app.post("/deploy", deploymentController.deployment); const PORT = process.env.PORT || 5000; app.listen(PORT, () => { console.log(`🚀 Server running on port ${PORT}`); + console.log(`Automated scheduler is active and will run daily at 2:00 AM`); });