Summary#
A run starts when its item is created, and an event happens when it is raised — which is right for live work and wrong for history. Import five hundred tickets and every SLA clock starts at the moment of the import, so the queue reads 0:59, 0:59, 3:59, 3:59 and a ticket opened a week ago is as fresh as one opened a minute ago. Two surfaces put the history's own times back:
StartedAt = this.Item.OpenedAt;— a workflow setting: the run is taken to have started then, and every clock it arms at the start counts from it.W.RaiseX(item, …, at: when)— the event happenedwhen: the run moves as of that instant, and every clock the move arms counts from it.
Signature#
workflow SupportTicket {
Tracks = Ticket.Stage;
Autostart = true;
StartedAt = this.Item.Conversation.StartedAt; // a live ticket: now; an imported one: when it was opened
…
}
SupportTicket.RaiseReply(t, at: firstReplyAt);
SupportTicket.RaiseHold(t, "Waiting on the carrier", at: heldAt);Description#
When does a run with StartedAt start?#
At the instant the setting names, evaluated once as the run starts — never later than now (a run cannot start in the
future, so a value after now is simply now). A live item whose field holds "now" starts exactly as it always did, which
is why a helpdesk can write StartedAt = this.Item.Conversation.StartedAt; for every ticket rather than only for
imported ones. A clock whose promise had already run out by the time of the import is missed straight away — the
breach is recorded, as it would have been had the run been there all along.
What may at: be?#
A DateTime no earlier than the moment the run entered the state it is in, and no later than now. History is raised
in the order it happened: an at: before the run's current state is refused, naming both instants, and so is one in
the future. Both refusals are ValidationException. An event that itself declares a parameter called at keeps it —
the parameter wins, as a C# named argument would.
Examples#
using Osysharp.Workflow;
enum Stage { New, Open, Done }
entity Ticket {
[Required, MaxLength(200)] string Title;
DateTime OpenedAt;
Stage Stage;
security { allow read, create, update when IsAuthenticated; }
}
workflow Desk {
Tracks = Ticket.Stage;
Autostart = true;
Initial = New;
StartedAt = this.Item.OpenedAt;
event Reply();
event Close();
state New {
Expire = TimeSpan.FromHours(4);
on Expire { }
subscribe Reply();
on Reply { goto Open; }
}
state Open {
on Close { goto Done; }
}
terminal success Done { }
}
/// One ticket from the old system: opened, and answered, when it really was.
public void ImportTicket(string title, DateTime opened, DateTime? answered) {
var t = new Ticket { Title = title, OpenedAt = opened };
UnitOfWork.Commit();
if (answered != null) { Desk.RaiseReply(t, at: answered.Value); }
}Testing it#
Read the clock the way a list does — Workflow.WorkByItem<Ticket>()'s BreachesAt — and assert it counts from the
history, not from the test: a ticket opened three hours ago on a four-hour promise has about an hour left.
See also#
- Raising a workflow event — raising an event, the live form of
at: - ServiceHours (SLA-accrual windows) — the calendar every clock counts on
- Assigned / Finished (milestones) — the clocks a move arms
- Tracks and Initial (the field a workflow drives) — the item a run is bound to