Skip to content

Background jobs

What it is

ManpowerIQ uses Hangfire for exactly one thing: running the Excel employee-import as a fire-and-forget background job so a large upload doesn't block the HTTP request. There are no scheduled or recurring jobs in v1 — no cron-style sweeps, no timed dispatch of any kind.

Both halves of that sentence matter, so this page states them plainly:

  • Fire-and-forget import job — built and live.
  • Scheduled/recurring jobs — none exist.

Why it's built this way

Bulk employee import parses three spreadsheet sheets and upserts many rows; doing that inline would tie up the request and risk timeouts. So import is enqueued as an async Hangfire job that transitions through a tracked lifecycle, while the API returns immediately with a job record the UI can poll (sheet 03 §how-it-works, §decisions). That single use case is the only thing that needed a background worker — so Hangfire was wired for it and nothing else.

The jobs a reader might expect from the handover (certification-expiry sweep, notification dispatch) were not built as jobs — see Gotchas.

How it works

sequenceDiagram
    participant U as User
    participant C as ImportsController
    participant H as Hangfire queue "imports"
    participant R as ImportJobRunner
    participant DB as PostgreSQL
    U->>C: upload .xlsx
    C->>C: synchronous pre-flight (size/ext/schema)
    C->>DB: create import_jobs row (Queued)
    C->>H: enqueue job
    C-->>U: 201 + job record
    H->>R: run [Queue("imports")]
    R->>DB: Queued → Running → Completed / Failed
    U->>C: poll job status / download error report
  • The import job is a real async pipeline on the Hangfire queue imports, not inline and not a stubImportJobRunner carries the [Queue("imports")] attribute and drives the job from Queued → Running → Completed (sheet 03 §build-status + §decisions, ImportJobRunner.cs:18, ImportsController.cs:143).
  • Pre-flight validation is synchronous (non-empty, ≤10 MB, .xlsx, schema check) and returns 400 before anything is enqueued (sheet 03 §rules, ImportsController.cs:67-102).
  • The job runs without an HTTP context, so it sets a manual tenant override and the Postgres RLS GUC itself; it skips the connection initializer under the EF InMemory provider (sheet 03 edge-cases, ImportJobRunner.cs:33-51).
  • Lifecycle states: Queued → Running → (Completed | CompletedWithErrors | Failed); a user cancel (SuperAdmin + imports.view_history) moves it to Cancelled and the worker checks the cancel token cooperatively between rows (sheet 03 §rules).

Gotchas / constraints

  • No recurring/scheduled jobs. Verified: grep for Hangfire RecurringJob → no matches (the only hits were EF ValueGeneratedOnAddOrUpdate false positives). Don't document a cron-style scheduler — there isn't one.
  • Certification expiry has no auto-sweep. Certs do not transition Active→Expired on a timer; there is no background job for it (sheet 04 §build-status). Any "expired" state is computed/visible only where the read logic does so, not swept by a job.
  • Notifications have no backend job. The pending-count bell is a frontend poll every 30s against live counts; there is no backend notification system — no sender, template, dispatcher, or entity (sheet 18 §build-status). It is not a Hangfire job.
  • The import job is provider-sensitive. Because it sets the tenant/RLS GUC manually and branches on the provider, it can behave differently under EF InMemory vs Postgres — cover it with a Postgres integration test, not just InMemory unit tests (sheet 03 edge-cases).
  • Uploaded file is in-memory only (never written to disk), SHA-256 hashed; a duplicate within 24h warns but does not block (sheet 03 §rules).

Build status

  • Available — the Hangfire fire-and-forget Excel import job (ImportJobRunner, queue imports), end-to-end with a job table and error-report regeneration (sheet 03 §build-status).
  • Planned / absent — any scheduled or recurring job: certification-expiry sweep (sheet 04), backend notification dispatch (sheet 18). No scheduled jobs in v1.
  • Multi-tenancy — how the job sets the tenant GUC without an HTTP context.
  • Fact sheets: 03 (employee master — import), 04 (skills & certifications), 18 (notifications).