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 \ trigger | Update / UpdateTags / AddItems / Media | Complete | Cancel | Delete |
|---|---|---|---|---|
Open | → InProgress | → Complete (CompletedOn) [+ item move] | → Cancelled | soft-delete ✔ |
InProgress | stays InProgress | → Complete [+ item move] | → Cancelled | ✔ |
Complete | 400 InvalidOperation "cannot be updated" | 200 idempotent no-op | 400 "cannot be Cancelled" | ✔ (no gate) |
Cancelled | 400 "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:
updateservicerecordupdateservicerecordtags- adding items (
CreateServiceItems) or removing them (DeleteServiceItem,DeleteServiceItems) - media upload/delete (
uploadmedia,deletemedia)
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.
ServiceRecordCancelled eventCancel 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. ServiceNumberuniqueness is scoped to non-terminal records — completing or cancelling a record frees itsServiceNumberfor 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.
Related
- Tasks & schedules —
ServiceRecordCompletedauto-completes linked task actions. - Item lifecycle — the
Movedstate andSourceType.ServiceRecordmovement history written on completion. - Errors & response conventions — the 400 error-array shape used by the locked-state rejections.