Skip to main content

Service records

A service record tracks a real-world service job — a delivery, pickup, repair, or maintenance visit — with the items involved attached as service items. Every record carries a string Status with a simple state machine: "Open", "InProgress", "Complete", "Cancelled".

Note the spelling: Complete, not Completed — it differs from the task domain's TaskMasterState. Match strings accordingly.

States and transitions

From \ triggerUpdate / UpdateTags / AddItems / MediaCompleteCancelDelete
OpenInProgressComplete (CompletedOn) [+ item move]Cancelledsoft-delete ✔
InProgressstays InProgressComplete [+ item move]Cancelled
Complete400 InvalidOperation "cannot be updated"200 idempotent no-op400 "cannot be Cancelled"✔ (no gate)
Cancelled400 "cannot be updated"400 "cannot be Completed"200 idempotent no-op

Any mutation forces InProgress

A record is created Open via createservicerecord — unless ServiceItems are supplied inline, in which case it starts InProgress immediately. From then on, every mutating operation bumps an Open record to InProgress:

ScannedQty is auto-maintained as the count of live service items on every item add/delete — don't try to set it yourself.

Completing — and the item move

CompleteServiceRecord sets Complete and stamps CompletedOn. If MoveItemsOnComplete is true, or the record's service type is a system Deliver/Pickup type, completion also moves the record's items to ToPlaceId — the only item mutation the Services domain performs. The move writes item movement history with SourceType.ServiceRecord and sets each item's state to Moved (see the Item lifecycle).

Completion also raises the ServiceRecordCompleted event, which is one of the cross-domain triggers that auto-completes a linked system-type task action — cascading a task toward Completed (see Tasks & schedules).

Cancelling

CancelServiceRecord sets Cancelled with no item movement. It is rejected (400) from Complete, just as Complete is rejected from Cancelled — the two terminal states cannot cross into each other.

No ServiceRecordCancelled event

Cancel raises only the generic ServiceRecordStateChanged event — there is no dedicated ServiceRecordCancelled event, an asymmetry with ServiceRecordCompleted. Subscribers that need to detect cancellation must watch ServiceRecordStateChanged and inspect the status. (Owner review of the asymmetry is pending.)

Terminal states are locked — except for delete

Complete and Cancelled lock the record: updates, tag changes, item adds/deletes, media changes, and cross-transitions all return 400 InvalidOperation with the status name in Field (see Errors & response conventions). Re-issuing the same terminal transition is an idempotent 200 no-op.

Two escape hatches:

  • Delete is allowed from any state, including Complete — soft-delete is not lifecycle-gated.
  • ServiceNumber uniqueness is scoped to non-terminal records — completing or cancelling a record frees its ServiceNumber for reuse by a new record.

Audit and events

No transaction-history table — the audit trail is the event stream plus the standard audit columns. Events: ServiceRecordCreated, ServiceRecordStateChanged (any status change), ServiceRecordUpdated, ServiceRecordCompleted, ServiceRecordItemCreated/Updated/Deleted, ServiceRecordDeleted.