enterprise_assest_managemen.../node_api/jobs/scheduler.js

240 lines
5.8 KiB
JavaScript

// node_api/jobs/scheduler.js
const cron = require('node-cron');
const { logger } = require('../utils/logger');
const { depreciationJob } = require('./depreciationJob');
const { JobStatus } = require('../models/JobStatus');
class JobScheduler {
constructor() {
this.jobs = new Map();
this.isInitialized = false;
}
async initialize() {
if (this.isInitialized) {
logger.warn('Job scheduler already initialized');
return;
}
try {
await this.setupJobs();
this.isInitialized = true;
logger.info('Job scheduler initialized successfully');
} catch (error) {
logger.error('Failed to initialize job scheduler:', error);
throw error;
}
}
async setupJobs() {
// Monthly depreciation job - runs on 1st of each month at 2 AM
const depreciationJobSchedule = '0 2 1 * *';
const depreciationCronJob = cron.schedule(
depreciationJobSchedule,
async () => {
await this.runJob('depreciation', depreciationJob);
},
{
scheduled: false,
timezone: 'America/New_York'
}
);
this.jobs.set('depreciation', {
cronJob: depreciationCronJob,
schedule: depreciationJobSchedule,
name: 'Monthly Depreciation Calculation',
lastRun: null,
nextRun: null,
status: 'stopped'
});
logger.info('Jobs configured:', Array.from(this.jobs.keys()));
}
async runJob(jobName, jobFunction) {
const job = this.jobs.get(jobName);
if (!job) {
logger.error(`Job ${jobName} not found`);
return;
}
const startTime = new Date();
const jobExecutionId = `${jobName}_${startTime.getTime()}`;
try {
logger.info(`Starting job: ${jobName} (ID: ${jobExecutionId})`);
// Update job status
job.status = 'running';
job.lastRun = startTime;
// Log job start in database
await JobStatus.create({
job_name: jobName,
execution_id: jobExecutionId,
status: 'running',
started_at: startTime,
details: { message: 'Job started' }
});
// Execute the job function
const result = await jobFunction();
// Job completed successfully
const endTime = new Date();
const duration = endTime - startTime;
job.status = 'completed';
job.lastRun = endTime;
await JobStatus.update(
{
status: 'completed',
completed_at: endTime,
duration_ms: duration,
details: result
},
{
where: { execution_id: jobExecutionId }
}
);
logger.info(`Job ${jobName} completed successfully in ${duration}ms`, result);
} catch (error) {
// Job failed
const endTime = new Date();
const duration = endTime - startTime;
job.status = 'failed';
job.lastRun = endTime;
await JobStatus.update(
{
status: 'failed',
completed_at: endTime,
duration_ms: duration,
error_message: error.message,
details: { error: error.stack }
},
{
where: { execution_id: jobExecutionId }
}
);
logger.error(`Job ${jobName} failed:`, error);
// Send notification about job failure
await this.notifyJobFailure(jobName, error);
}
}
async notifyJobFailure(jobName, error) {
// TODO: Implement email notifications or webhook calls
logger.error(`Job failure notification for ${jobName}:`, error.message);
}
startAll() {
for (const [name, job] of this.jobs) {
job.cronJob.start();
job.status = 'scheduled';
job.nextRun = this.getNextRunTime(job.schedule);
logger.info(`Started job: ${name}, next run: ${job.nextRun}`);
}
}
stopAll() {
for (const [name, job] of this.jobs) {
job.cronJob.stop();
job.status = 'stopped';
job.nextRun = null;
logger.info(`Stopped job: ${name}`);
}
}
async runManual(jobName) {
const job = this.jobs.get(jobName);
if (!job) {
throw new Error(`Job ${jobName} not found`);
}
if (job.status === 'running') {
throw new Error(`Job ${jobName} is already running`);
}
// Get the job function
const jobFunction = this.getJobFunction(jobName);
if (!jobFunction) {
throw new Error(`Job function for ${jobName} not found`);
}
return await this.runJob(jobName, jobFunction);
}
getJobFunction(jobName) {
const jobFunctions = {
'depreciation': depreciationJob
};
return jobFunctions[jobName];
}
getNextRunTime(schedule) {
try {
const task = cron.schedule(schedule, () => {}, { scheduled: false });
return task.nextDate();
} catch (error) {
logger.error('Error calculating next run time:', error);
return null;
}
}
getJobStatus(jobName) {
const job = this.jobs.get(jobName);
if (!job) {
return null;
}
return {
name: job.name,
status: job.status,
schedule: job.schedule,
lastRun: job.lastRun,
nextRun: job.nextRun
};
}
getAllJobStatuses() {
const statuses = {};
for (const [name, job] of this.jobs) {
statuses[name] = this.getJobStatus(name);
}
return statuses;
}
async getJobHistory(jobName, limit = 50) {
try {
const history = await JobStatus.findAll({
where: { job_name: jobName },
order: [['started_at', 'DESC']],
limit: limit
});
return history;
} catch (error) {
logger.error('Error fetching job history:', error);
return [];
}
}
destroy() {
this.stopAll();
this.jobs.clear();
this.isInitialized = false;
logger.info('Job scheduler destroyed');
}
}
// Export singleton instance
const jobScheduler = new JobScheduler();
module.exports = { jobScheduler };