Skip to content

Report Scheduling Guide

Table of Contents

Introduction

Schedulers automate the execution of reports, workers, and reminders based on recurring schedules (cron expressions) or one-time dates. This guide covers how to create, configure, and manage schedulers in the Syneo/Barcoding system.

Creating Schedulers

Via Web Application

Schedulers are typically created through the web application interface:

  1. Navigate to the Scheduler management page
  2. Click "Create New Scheduler"
  3. Fill in the required fields:
    • Description: Human-readable description
    • Type: Report, Worker, or Reminder
    • Schedule Type: Recurring (cron) or One-time (date)
    • Schedule: Cron expression or date/time
    • Parameters: Task-specific parameters (JSON)
    • Status: Active/Inactive

Via API

Create a recurring scheduler:

http
POST /schedulers
Content-Type: application/json

{
  "description": "Daily sales report at 8 AM",
  "report_id": 25,
  "schedule": "0 8 * * *",
  "status": true,
  "params": {
    "controller": "RefreshUserToken",
    "options_interface": {
      "delivery": {
        "email": true,
        "instant": false
      },
      "output": {
        "file": {
          "type": "Direct",
          "format": "PDF"
        }
      }
    }
  },
  "user_id_created": 1
}

Create a one-time scheduler:

http
POST /schedulers
Content-Type: application/json

{
  "description": "Send reminder on December 15th at 3 PM",
  "reminder_id": 10,
  "date_time": "2025-12-15T15:00:00.000Z",
  "status": true,
  "params": {
    "message": "Your appointment is tomorrow"
  },
  "user_id_created": 1
}

Via Database

Directly insert into the Scheduler table:

sql
INSERT INTO Scheduler (
  description,
  report_id,
  schedule,
  status,
  params,
  user_id_created
) VALUES (
  'Daily token refresh',
  25,
  '0 8 * * *',
  1,
  '{"controller": "RefreshUserToken", "options_interface": {...}}',
  1
);

Scheduler Types

1. Report Schedulers

Execute reports automatically based on schedule.

Example: Daily Sales Report

json
{
  "description": "Daily sales report at 8 AM",
  "report_id": 25,
  "schedule": "0 8 * * *",
  "status": true,
  "params": {
    "controller": "SalesReport",
    "options_interface": {
      "delivery": {
        "email": true,
        "instant": false,
        "deliver_to": {
          "user_id": [1, 2, 3]
        }
      },
      "output": {
        "file": {
          "type": "Direct",
          "format": "PDF"
        }
      }
    },
    "report_params": {
      "start_date": "2025-12-01",
      "end_date": "2025-12-31"
    }
  }
}

Example: Hourly Token Refresh

json
{
  "description": "Refresh user tokens every hour",
  "report_id": 30,
  "schedule": "0 * * * *",
  "status": true,
  "params": {
    "controller": "RefreshUserToken",
    "options_interface": {
      "delivery": {
        "instant": false
      }
    }
  }
}

2. Worker Schedulers

Execute workers (background jobs) based on schedule.

Example: Data Sync Worker

json
{
  "description": "Sync external data every 30 minutes",
  "worker_id": 5,
  "schedule": "*/30 * * * *",
  "status": true,
  "params": {
    "source": "external_api",
    "batch_size": 1000
  }
}

3. Reminder Schedulers

Send reminders at scheduled times.

Example: One-Time Meeting Reminder

json
{
  "description": "Meeting reminder for December 15th",
  "reminder_id": 10,
  "date_time": "2025-12-15T14:00:00.000Z",
  "status": true,
  "params": {
    "message": "Team meeting starts in 1 hour",
    "recipients": [1, 2, 3, 4, 5]
  }
}

4. Stay-Alive Schedulers

Special type for long-running, continuously active processes.

Characteristics:

  • stay_alive: true flag set
  • Not processed by cron-scheduler (runs in standalone container)
  • Automatically restarts if it crashes
  • Used for services that need to be always running

Example: Real-time Monitoring Worker

json
{
  "description": "Real-time system monitor",
  "worker_id": 15,
  "stay_alive": true,
  "status": true
}

Note: Stay-alive schedulers are excluded from cron-scheduler processing:

typescript
where: {
  status: true,
  stay_alive: false  // Exclude stay-alive schedulers
}

Scheduler Types

Recurring Schedulers (Cron Expression)

Uses the schedule field with cron syntax.

Database Field:

typescript
schedule: string  // e.g., "0 8 * * *"

Execution:

  • Evaluated every minute by cron-scheduler
  • Executes when current time matches cron expression
  • Continues executing on schedule until disabled

Use Cases:

  • Daily, weekly, monthly reports
  • Periodic data synchronization
  • Regular maintenance tasks

One-Time Schedulers (Date/Time)

Uses the date_time field with ISO 8601 date string.

Database Field:

typescript
date_time: Date  // e.g., "2025-12-15T15:00:00.000Z"

Execution:

  • Evaluated every minute by cron-scheduler
  • Executes when current time matches scheduled time (within the same minute)
  • Automatically disabled after execution

Use Cases:

  • One-time reminders
  • Scheduled future tasks
  • Time-specific actions

Cron Expression Syntax

Cron expressions define recurring schedules using 5 fields:

* * * * *
│ │ │ │ │
│ │ │ │ └─── Day of week (0-6, Sunday=0)
│ │ │ └───── Month (1-12)
│ │ └─────── Day of month (1-31)
│ └───────── Hour (0-23)
└─────────── Minute (0-59)

Syntax Elements

SymbolMeaningExample
*Any value* * * * * = Every minute
5Specific value5 * * * * = At minute 5 of every hour
1-5Range0 8-17 * * * = Every hour from 8 AM to 5 PM
*/5Step values*/5 * * * * = Every 5 minutes
1,3,5List0 8 * * 1,3,5 = 8 AM on Mon, Wed, Fri

Common Examples

Every Minute:

* * * * *

Every Hour:

0 * * * *

Every Day at 8:00 AM:

0 8 * * *

Every Hour from 9 AM to 5 PM:

0 9-17 * * *

Every 5 Minutes:

*/5 * * * *

Every 30 Minutes:

*/30 * * * *

or

0,30 * * * *

Every Monday at 9:00 AM:

0 9 * * 1

Every Weekday at 6:00 PM:

0 18 * * 1-5

First Day of Month at Midnight:

0 0 1 * *

Every Quarter (Jan, Apr, Jul, Oct) on 1st at Midnight:

0 0 1 1,4,7,10 *

Every 2 Hours:

0 */2 * * *

Every 15 Minutes During Business Hours (9 AM - 5 PM):

*/15 9-17 * * *

Weekend Days at 10:00 AM:

0 10 * * 0,6

Validation

The cron-scheduler validates expressions before evaluating:

typescript
if (!cron.validate(cronExpression)) {
  console.error(`Invalid cron expression: ${cronExpression}`);
  return false;
}

Invalid expressions will be logged and skipped.

Testing Cron Expressions

Use online tools to test cron expressions:

Or test in Node.js:

javascript
const cron = require('node-cron');

const expression = '0 8 * * *';
console.log(cron.validate(expression)); // true or false

// Test against specific date
const task = cron.schedule(expression, () => {
  console.log('Task would execute now!');
}, {
  scheduled: false
});

Scheduler Parameters

The params field stores task-specific configuration as JSON.

Report Parameters

json
{
  "controller": "SalesReport",
  "options_interface": {
    "delivery": {
      "email": true,
      "instant": false,
      "deliver_to": {
        "user_id": [1, 2, 3],
        "group_id": [5]
      }
    },
    "output": {
      "file": {
        "type": "Direct",
        "format": "PDF",
        "filename": "sales_report_{{date}}.pdf"
      }
    }
  },
  "report_params": {
    "start_date": "2025-12-01",
    "end_date": "2025-12-31",
    "include_charts": true
  }
}

Key Fields:

  • controller: Report class name (e.g., "SalesReport")
  • options_interface.delivery:
    • email: Send via email (boolean)
    • instant: Execute in foreground (boolean) - forced to true in cron context
    • deliver_to: User/group IDs to deliver to
  • options_interface.output.file:
    • type: "Direct" or "Attachment"
    • format: "PDF", "Excel", "CSV", etc.
    • filename: Output filename (supports templates)
  • report_params: Report-specific parameters

Worker Parameters

json
{
  "source": "external_api",
  "batch_size": 1000,
  "timeout": 30000,
  "retry_on_failure": true
}

Worker parameters are passed as command-line arguments:

typescript
// Scheduler params: {"source": "external_api", "batch_size": 1000}
// Becomes: ["source=external_api", "batch_size=1000", "scheduler_id=16"]

Dynamic Parameters

Parameters can include dynamic values using templates:

json
{
  "filename": "report_{{date}}.pdf",
  "date_range": {
    "start": "{{yesterday}}",
    "end": "{{today}}"
  }
}

Template Variables (if implemented in report):

  • : Current date
  • : Yesterday's date
  • : Today's date
  • : Current user ID

Managing Schedulers

Enable/Disable Scheduler

Via API:

http
PATCH /schedulers/16
Content-Type: application/json

{
  "status": false  // Disable scheduler
}

Via Database:

sql
UPDATE Scheduler SET status = 0 WHERE id = 16;

Effect: Disabled schedulers are not evaluated by cron-scheduler.

Update Schedule

http
PATCH /schedulers/16
Content-Type: application/json

{
  "schedule": "0 9 * * *"  // Change from 8 AM to 9 AM
}

Effect: Takes effect on next cron-scheduler evaluation (within 1 minute).

Update Parameters

http
PATCH /schedulers/16
Content-Type: application/json

{
  "params": {
    "controller": "SalesReport",
    "report_params": {
      "include_charts": false  // Disable charts
    }
  }
}

Delete Scheduler

http
DELETE /schedulers/16

Warning: This permanently deletes the scheduler. Consider disabling (status: false) instead.

View Execution History

Query the SchedulerExecution table:

sql
SELECT
  se.id,
  se.execution_key,
  se.executed_at,
  se.container_id,
  s.description
FROM SchedulerExecution se
JOIN Scheduler s ON s.id = se.scheduler_id
WHERE se.scheduler_id = 16
ORDER BY se.executed_at DESC
LIMIT 10;

Example Result:

id  | execution_key        | executed_at              | container_id       | description
----|----------------------|--------------------------|-------------------|------------------
156 | cron-2025-12-12-8-0  | 2025-12-12 08:00:00.123 | hostname-12345    | Daily token refresh
155 | cron-2025-12-11-8-0  | 2025-12-11 08:00:00.456 | hostname-12345    | Daily token refresh
154 | cron-2025-12-10-8-0  | 2025-12-10 08:00:00.789 | hostname-67890    | Daily token refresh

Check Failed Executions

Query the RabbitMQ failed queue:

bash
docker exec rabbitmq rabbitmqadmin get queue=cron_scheduler_failed count=10

Or check worker logs:

bash
grep "PERMANENTLY FAILED" cron-worker.log

Testing and Debugging

Test Scheduler Immediately

Option 1: Set date_time to current time

sql
UPDATE Scheduler
SET date_time = NOW() + INTERVAL 1 MINUTE
WHERE id = 16;

Wait 1 minute for execution.

Option 2: Manually publish to queue

bash
# Publish message directly to RabbitMQ
docker exec rabbitmq rabbitmqadmin publish \
  exchange=amq.default \
  routing_key=cron_scheduler \
  payload='{"type":"report","id":25,"scheduler_id":16,"params_scheduler":"{}"}'

Debug Scheduler Not Executing

Check 1: Is scheduler active?

sql
SELECT id, description, status, schedule, date_time
FROM Scheduler
WHERE id = 16;

Expected: status = 1

Check 2: Is cron expression valid?

javascript
const cron = require('node-cron');
console.log(cron.validate('0 8 * * *'));  // Should be true

Check 3: Does cron expression match current time?

bash
# Check current time
date
# Expected format: Fri Dec 12 08:00:00 UTC 2025

# If schedule is "0 8 * * *", it should execute at 08:00:00

Check 4: Was execution already claimed?

sql
SELECT * FROM SchedulerExecution
WHERE scheduler_id = 16
  AND DATE(executed_at) = CURDATE()
ORDER BY executed_at DESC;

If record exists with today's date and matching execution_key, it was already executed.

Check 5: Is cron-scheduler running?

bash
ps aux | grep cron-scheduler

Check 6: Check cron-scheduler logs

bash
tail -f cron-scheduler.log

# Look for:
# ✓ Claimed scheduler 16: Daily token refresh
# ✓ Published: report 25

Debug Task Execution Failures

Check 1: Worker logs

bash
tail -f cron-worker.log

# Look for:
# [ERROR] [type=report, id=25, scheduler_id=16] Task execution failed
# [ERROR] Stack trace: ...

Check 2: Check retry count

bash
grep "retry" cron-worker.log | grep "scheduler_id=16"

# Look for:
# [WARN] [scheduler_id=16] Scheduling retry 1/3 in 30000ms
# [WARN] [scheduler_id=16] Scheduling retry 2/3 in 60000ms

Check 3: Check failed queue

bash
docker exec rabbitmq rabbitmqadmin list queues name messages

# Look for non-zero message count in cron_scheduler_failed

Check 4: Inspect failed message

bash
docker exec rabbitmq rabbitmqadmin get queue=cron_scheduler_failed

# Output includes:
# - payload: Task details
# - failed_reason: Error message
# - retry_count: Number of retries attempted

Monitor Scheduler Performance

Check execution duration:

bash
grep "duration" cron-worker.log | grep "scheduler_id=16"

# Example output:
# 2025-12-12T08:00:05.123Z [SUCCESS] [scheduler_id=16, duration=5123ms] Task completed

Check queue depth:

bash
docker exec rabbitmq rabbitmqadmin list queues name messages

# Output:
# cron_scheduler          5   (5 tasks pending)
# cron_scheduler_retry    2   (2 tasks waiting for retry)
# cron_scheduler_failed   1   (1 permanently failed task)

Best Practices

1. Use Descriptive Scheduler Names

json
{
  "description": "Daily sales report at 8 AM (Mon-Fri)",
  // Not: "Report 1"
}

2. Set Appropriate Schedules

  • Frequent reports: Avoid overlapping executions

    ❌ BAD: */5 * * * *  (every 5 minutes) for a 10-minute report
    ✅ GOOD: */15 * * * * (every 15 minutes) for a 10-minute report
  • Resource-intensive tasks: Schedule during off-peak hours

    ✅ GOOD: 0 2 * * *  (2 AM) for heavy database queries

3. Design Idempotent Tasks

Tasks should produce the same result when executed multiple times:

typescript
// ❌ BAD: Not idempotent
async mainFunction() {
  await this.db.query('UPDATE counters SET value = value + 1');
}

// ✅ GOOD: Idempotent
async mainFunction() {
  const date = new Date().toISOString().split('T')[0];
  await this.db.query(
    'INSERT INTO daily_stats (date, value) VALUES (?, 1) ' +
    'ON DUPLICATE KEY UPDATE value = 1',
    [date]
  );
}

4. Handle Transient vs Permanent Errors

typescript
async mainFunction() {
  try {
    await this.externalAPI.fetch();
  } catch (err) {
    if (err.code === 'ECONNREFUSED' || err.code === 'ETIMEDOUT') {
      // Transient error - throw to trigger retry
      throw err;
    } else if (err.code === 'AUTH_INVALID') {
      // Permanent error - log and don't throw
      await this.log(LogType.Error, 'Invalid credentials - manual intervention required');
      return; // Don't throw - retrying won't help
    }
  }
}

5. Use Appropriate Notification Methods

json
{
  "params": {
    "options_interface": {
      "delivery": {
        "email": true,           // For reports to be reviewed
        "web_notification": true, // For quick alerts
        "instant": false          // Let it run in background (forced to true by worker)
      }
    }
  }
}

6. Test Before Enabling

  1. Create scheduler with status: false
  2. Manually test execution
  3. Verify output/logs
  4. Enable with status: true

7. Monitor Failed Executions

Set up alerts for failed tasks:

bash
# Check failed queue daily
#!/bin/bash
failed_count=$(docker exec rabbitmq rabbitmqadmin list queues -f tsv | grep cron_scheduler_failed | awk '{print $2}')

if [ "$failed_count" -gt 0 ]; then
  echo "ALERT: $failed_count failed tasks in queue!"
  # Send notification
fi

8. Cleanup Old Schedulers

Regularly review and disable/delete unused schedulers:

sql
-- Find schedulers not executed in 30 days
SELECT s.id, s.description, MAX(se.executed_at) as last_execution
FROM Scheduler s
LEFT JOIN SchedulerExecution se ON s.id = se.scheduler_id
WHERE s.status = 1
GROUP BY s.id
HAVING last_execution IS NULL OR last_execution < DATE_SUB(NOW(), INTERVAL 30 DAY);

9. Document Parameters

Add comments to complex parameter configurations:

json
{
  "description": "Monthly sales report with regional breakdown",
  "params": {
    "controller": "SalesReport",
    "report_params": {
      "aggregation": "monthly",  // Groups data by month
      "regions": ["NA", "EU", "APAC"],  // North America, Europe, Asia-Pacific
      "include_forecast": true  // Includes predictive analytics
    }
  }
}

10. Version Control Scheduler Definitions

Store scheduler configurations in version control:

json
// schedulers/daily-sales-report.json
{
  "description": "Daily sales report at 8 AM",
  "report_id": 25,
  "schedule": "0 8 * * *",
  "status": true,
  "params": { /* ... */ }
}

Import using scripts:

bash
#!/bin/bash
for file in schedulers/*.json; do
  curl -X POST http://localhost:3001/schedulers \
    -H "Content-Type: application/json" \
    -d @"$file"
done

Syneo/Barcoding Documentation