SAP Job Scheduler Architecture — Internal Technical Reference

Created by Lori A, Modified on Tue, 31 Mar at 5:35 AM by Lori A

Version 5.2 — Last updated: March 2026 — Internal Technical Reference

1. Architecture Overview

FocusPoint uses Quartz.NET with a persistent ADO Job Store (SQL Server) for SAP integration jobs. Jobs can run in two mutually exclusive locations:

ComponentProcessScheduler NamePurpose
Core SchedulerFocusPoint.Web.UI (IIS / Kestrel)FocusPoint.Core.SchedulerRuns all platform jobs + SAP jobs (when enabled)
SAP Worker SchedulerFocusPoint.IntegrationWorker (Windows Service)FocusPoint.SapWorker.SchedulerRuns SAP jobs in an isolated process

Both schedulers share the same Quartz tables (prefix: SCHEDULEJOB_) in the application database but use different SCHED_NAME values to isolate their job data.

2. Operating Modes

The master toggle is SapIntegration:EnableSapJobs in App_Data/appsettings.json:

SettingSAP Jobs Run InWeb App BehaviorWorker Behavior
"EnableSapJobs": trueWeb App (Core Scheduler)Registers & schedules all SAP jobs on FocusPoint.Core.Scheduler. Cleans up orphaned SapWorker rows.Worker should not be running, or will duplicate work.
"EnableSapJobs": falseIntegration Worker (SapWorker Scheduler)Does not register SAP job types. Removes orphaned SAP job entries from core scheduler via SQL.Creates dedicated scheduler, registers & runs all SAP jobs. Cleans up orphaned SAP jobs from core scheduler.

Important: Only ONE mode should be active at a time. Running both will cause duplicate message processing, race conditions, and data corruption.

3. Scheduler Instances

PropertyCore SchedulerSAP Worker Scheduler
Scheduler NameFocusPoint.Core.SchedulerFocusPoint.SapWorker.Scheduler
DB Table PrefixSCHEDULEJOB_ (shared)
Job GroupMain (shared)
ClusteringYesYes
Max Concurrency10 (default)5
Misfire Threshold60s (default)240s
Hosted ByAddQuartzHostedService in ASP.NETManual StartSapScheduler() call
ContainsPlatform jobs + SAP jobs (when enabled)SAP jobs only

4. SAP Job Registry

All SAP jobs are defined in SapServiceRegistration.ScheduleSapJobs() and mapped to interval groups:

GroupDefault IntervalMinJobs
FEEDER (high)30s15sFileReaderJob
CRITICAL (high)30s15sSalesOrderConfirmationJob, SalesQuoteConfirmationJob, OrderUpdateJob, QuoteUpdateJob, NewUserResponseJob, ShipmentAdviceJob, PaymentCaptureJob, InvoiceReceiptJob, SalesDraftReceiptJob
MEDIUM60s30sSapCustomerJob, BusinessPartnerContactJob, SalesEmployeeJob, TaxInfoJob, ItemMappingJob, JsonMessageJob, SapOuboundBPJob
HEAVY2x medium120sItemJob, InventoryJob, BPAddressesJob
LOW60s60sSapPromotionJob, ServiceContractJob, BusinessPartnerCategoryJob

Intervals are configurable via SAPIntegrationSettings in the admin panel and applied at startup via RescheduleJobIntervalsAsync().

5. Configuration Reference

App_Data/appsettings.json

KeyTypeDefaultDescription
SapIntegration:EnableSapJobsbooltrueMaster toggle. true = web app runs SAP jobs. false = worker runs them.
SapIntegration:WorkerEndpointUrlstring""Base URL of the worker's health/API endpoint (for forwarding requests).
SchedulerSettings:NodeNamestringIntegrationWorker-01Unique node ID. Used as Windows Service suffix and log file prefix.
HealthCheck:Portint5100Worker's HTTP port for /health and /ready endpoints.

Database Settings (Admin Panel)

SettingDefaultDescription
JobIntervalHighPrioritySeconds30Interval for FEEDER and CRITICAL jobs (min 15s)
JobIntervalMediumPrioritySeconds60Interval for MEDIUM jobs (min 30s)
JobIntervalLowPrioritySeconds60Interval for LOW jobs (min 60s)

6. Startup Behavior

Web App Startup (NopStartup.cs, Order = 5002)

When EnableSapJobs = true:
ConfigureServices → Register SAP Job Types (DI) → Configure() → Schedule SAP Jobs on Core Scheduler → Cleanup SapWorker Rows (SQL) → Reschedule Intervals from DB

When EnableSapJobs = false:
ConfigureServices → Skip SAP Job Registration → Configure() → Remove SAP Jobs from Core Scheduler (SQL) → Return (no further setup)

Worker Startup (Program.cs)

Build Host & DI → Create SAP Scheduler → Schedule SAP Jobs → Start Scheduler → Cleanup Core Scheduler SAP Jobs (SQL) → Reschedule Intervals from DB

7. Database Cleanup Logic

Cleanup is performed via CleanupSapSchedulerEntriesAsync() using direct SQL (not Quartz API) because the target scheduler instance may not be started or even created in the current process.

CallerParametersWhat It CleansWhy
Web App (EnableSapJobs=true)cleanupSapWorkerScheduler: trueAll rows where SCHED_NAME = 'FocusPoint.SapWorker.Scheduler'SapWorker rows are orphaned — no worker is running, web app owns SAP jobs.
Web App (EnableSapJobs=false)removeSapJobsFromCoreScheduler: trueSAP job rows where SCHED_NAME = 'FocusPoint.Core.Scheduler' and JOB_NAME IN (<SAP jobs>)SAP jobs in core scheduler are orphaned — worker owns them now. Non-SAP platform jobs are untouched.
Integration WorkerremoveSapJobsFromCoreScheduler: trueSame as above (targeted SAP jobs from core scheduler)Worker is taking over — ensure core scheduler doesn't also fire SAP jobs.

SQL Deletion Order (FK Dependencies)

Quartz tables have foreign key constraints. Rows must be deleted in this order:

StepTableReason
1SCHEDULEJOB_SIMPLE_TRIGGERSFK to TRIGGERS
2SCHEDULEJOB_CRON_TRIGGERSFK to TRIGGERS
3SCHEDULEJOB_SIMPROP_TRIGGERSFK to TRIGGERS
4SCHEDULEJOB_BLOB_TRIGGERSFK to TRIGGERS
5SCHEDULEJOB_FIRED_TRIGGERSIndependent
6SCHEDULEJOB_TRIGGERSFK to JOB_DETAILS
7SCHEDULEJOB_JOB_DETAILSRoot table
8SCHEDULEJOB_PAUSED_TRIGGER_GRPSOnly for full scheduler cleanup
9SCHEDULEJOB_SCHEDULER_STATEOnly for full scheduler cleanup
10SCHEDULEJOB_LOCKSOnly for full scheduler cleanup
11SCHEDULEJOB_CALENDARSOnly for full scheduler cleanup

Note: Steps 8–11 are only executed for full scheduler cleanup (cleanupSapWorkerScheduler). Targeted SAP job removal (removeSapJobsFromCoreScheduler) only deletes steps 1–7 filtered by job name.

8. Cache Invalidation (Worker Mode)

When the Integration Worker processes SAP inbound messages that modify products, the product price cache in the web app process must be invalidated. The worker cannot clear this cache directly because it lives in a separate process.

Flow:
Worker processes items → Collects product IDs in _processedItems queue → Publishes entity events → HTTP POST to web app

The worker sends a POST request to {FirstStoreUrl}/api/ClearProductPriceCache with the list of affected product IDs as form-encoded data. The web app's ApiController.ClearProductPriceCache enqueues them into ProductCacheClearQueue.

Condition: This HTTP call only happens when EnableSapJobs = false (worker mode) and there are processed items. In web app mode, cache is cleared in-process.


9. Setup & Deployment

Scenario A: SAP Jobs in Web App (Default)

No additional setup required. SAP jobs run on the core scheduler inside the web app process.

// App_Data/appsettings.json (web app)
{
  "SapIntegration": {
    "EnableSapJobs": true    // <-- default, can be omitted
  }
}

Scenario B: SAP Jobs in Integration Worker

Step 1: Configure Web App

// App_Data/appsettings.json (web app)
{
  "SapIntegration": {
    "EnableSapJobs": false,
    "WorkerEndpointUrl": "http://localhost:5100"
  }
}

Step 2: Deploy Worker via PowerShell

# Basic deployment (reads settings from appsettings.json)
.\deploy-worker.ps1

# Deploy for a specific client
.\deploy-worker.ps1 -NodeName "IntegrationWorker-Acme" `
                    -Database "AcmeDB" `
                    -DataSource "SQL01" `
                    -HealthCheckPort 5101

# Deploy multiple workers (multi-tenant)
.\deploy-worker.ps1 -NodeName "Worker-ClientA" -Database "ClientA_DB" -HealthCheckPort 5100
.\deploy-worker.ps1 -NodeName "Worker-ClientB" -Database "ClientB_DB" -HealthCheckPort 5101

Step 3: Verify

# Check service status
sc query "FocusPoint.IntegrationWorker.IntegrationWorker-Acme"

# Health check
curl http://localhost:5101/health

# Readiness check
curl http://localhost:5101/ready

Deploy Script Parameters

ParameterRequiredDefaultDescription
-NodeNameNoFrom appsettings or IntegrationWorker-01Unique identifier, used as service name suffix
-OutputPathNoC:\Services\FocusPoint.IntegrationWorker.<NodeName>Installation directory
-DatabaseNoFrom appsettingsSQL Server database name (shorthand)
-DataSourceNoFrom appsettingsSQL Server instance (shorthand)
-ConnectionStringNoFrom appsettingsFull connection string (overrides Database/DataSource)
-HealthCheckPortNo5100HTTP port for health endpoints
-UninstallNoStop and remove the Windows Service
-SkipPublishNoSkip dotnet publish, use existing binaries

10. Operational Commands

Windows Service Management

ActionCommand
Install service.\deploy-worker.ps1 -NodeName "Worker-01"
Start servicesc start "FocusPoint.IntegrationWorker.Worker-01"
Stop servicesc stop "FocusPoint.IntegrationWorker.Worker-01"
Check statussc query "FocusPoint.IntegrationWorker.Worker-01"
Uninstall service.\deploy-worker.ps1 -NodeName "Worker-01" -Uninstall
View logsGet-Content "C:\Services\...\Logs\Worker-01-*.log" -Tail 100 -Wait

Manual CLI (without deploy script)

ActionCommand
Run in consoledotnet run --project Workers/FocusPoint.IntegrationWorker
Install as service (built-in)FocusPoint.IntegrationWorker.exe --install
Uninstall service (built-in)FocusPoint.IntegrationWorker.exe --uninstall

Manual Database Cleanup (SQL)

-- View all SAP jobs in the core scheduler
SELECT JOB_NAME, JOB_GROUP, SCHED_NAME
FROM SCHEDULEJOB_JOB_DETAILS
WHERE SCHED_NAME = 'FocusPoint.Core.Scheduler'
  AND JOB_NAME IN ('FileReaderJob','SalesOrderConfirmationJob',
      'SalesQuoteConfirmationJob','OrderUpdateJob','QuoteUpdateJob',
      'NewUserResponseJob','ShipmentAdviceJob','PaymentCaptureJob',
      'InvoiceReceiptJob','SalesDraftReceiptJob','SapCustomerJob',
      'BusinessPartnerContactJob','SalesEmployeeJob','TaxInfoJob',
      'ItemMappingJob','JsonMessageJob','SapOuboundBPJob','ItemJob',
      'InventoryJob','BPAddressesJob','SapPromotionJob',
      'ServiceContractJob','BusinessPartnerCategoryJob');

-- View all SapWorker scheduler entries
SELECT JOB_NAME, JOB_GROUP, SCHED_NAME
FROM SCHEDULEJOB_JOB_DETAILS
WHERE SCHED_NAME = 'FocusPoint.SapWorker.Scheduler';

-- View scheduler state (which nodes are connected)
SELECT * FROM SCHEDULEJOB_SCHEDULER_STATE;

Warning: Do not manually delete Quartz rows while the owning scheduler is running. Stop the service first, then delete, then restart.

11. Troubleshooting

SymptomCauseFix
SAP jobs still in DB after setting EnableSapJobs=falseCleanup uses direct SQL and runs during Configure(). If the web app crashes before reaching that point, cleanup doesn't execute.Restart the web app. If the issue persists, run the cleanup SQL manually (see Section 10).
Duplicate message processingBoth web app and worker are running SAP jobs simultaneously.Ensure EnableSapJobs is false on the web app when using the worker. Restart the web app after changing the setting.
Worker starts but jobs don't fireSAP job types not registered in DI, or scheduler not started.Check worker logs for startup errors. Verify the worker's App_Data/appsettings.json has the correct connection string.
Product prices stale after worker processes itemsWorker cannot clear web app's in-memory cache directly.Verify the first store URL is reachable from the worker. Check worker logs for "Failed to notify web app to clear product price cache".
SCHEDULEJOB_LOCKS row stuckScheduler crashed while holding a DB lock.Stop all schedulers, delete the stuck lock row, restart.
Port conflict on workerMultiple workers using the same HealthCheck:Port.Assign unique ports per node: -HealthCheckPort 5100, 5101, etc.

FocusPoint Platform — SAP Job Scheduler Architecture — Internal Document — March 2026

Was this article helpful?

That’s Great!

Thank you for your feedback

Sorry! We couldn't be helpful

Thank you for your feedback

Let us know how can we improve this article!

Select at least one of the reasons
CAPTCHA verification is required.

Feedback sent

We appreciate your effort and will try to fix the article