ZuloOne
English
Sign in
Menu

Create a job

Updated

A job is code on a cron, without a button on a form: nightly recalc, migration, a pull that must keep going. It is a metadata object plus a Task script, not an event handler with a timer.

Note

Commit the trio together. The job row points at scriptMetaId; a .json without the .script.json + .cs pair fails the script-pair gate and import on a foreign key. The same rule as a command.

How to#

Three files in Jobs/<Name>/. The working example in Sales is RecalcAbcClassifications (nightly ABC classes). Accounting has DetachAutoJournals (idempotent: a second run is a no-op).

1. The job — <Name>.json, kind: "Job". Point scriptMetaId at the task. Set cronExpression (five-field cron), isActive, executeSingle: true so a second tick does not enqueue while the current run is still going, and queueTtlMinutes (how long to wait for a worker before the run is missed).

2. The binding — <Name>Task.script.json. scriptType is "Task", objectType is "Job", objectMetaId is the job's metaId.

3. The body — <Name>Task.cs. Inherit TaskScriptBase. Keep it thin: resolve a service, call it, log.

public class RecalcAbcClassificationsTask : TaskScriptBase
{
    public override async Task ExecuteAsync(TaskContext context)
    {
        var asOf = DateTime.UtcNow.Date;
        var classes = await ScriptServices.Get<IAbcClassifier>()
            .RecalcAllEnabledAsync(asOf);
        context.Log("classes=" + classes);
    }
}

The same root services as any script (GetService<T>() / ScriptServices.Get<T>()). There is no Chain of Command on a job: one job, one handler. Two jobs with the same cron must not own the same business step.

4. Bump modelVersion in the same change.

A test drives the run itself: Db.RunJobAsync(jobMetaId). The scheduler skips its tick while any integration test is in flight on the stand (a job commits on its own connection; a test rolls back). Run-now still runs.

Warning

On model import, jobs arrive disabled. A tenant that just installed the model will not fire the cron until someone turns isActive on. Leaving isActive: true in the file is the intended on-stand default; it is not what a fresh import does.

A Task script does not get the generated GlobalConstants class. Read a constant through GetService<IGlobalConstantManager>().Get<T>("Name") — Add a global constant.

What's not there yet#

A job is not a user command and has no menu item. There is no wrap of another model's job: if you need a different step, create a different job.

Where next#

Was this page helpful?