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.jsonwithout the.script.json+.cspair 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
isActiveon. LeavingisActive: truein 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.