Service ticket
A work order for one specific bike belonging to one specific customer. Has line items (labour and parts), status transitions, history, optional outbound messages.
Table: service_tickets
| Column | Type | Notes |
|---|---|---|
id | INTEGER PK | |
ticket_number | TEXT | Human-readable, e.g., T-N0001 |
customer_id | INTEGER FK → customers | |
customer_bike_id | INTEGER FK → customer_bikes | |
status | TEXT | One of: dropped_off, quoted, in_progress, awaiting_parts, ready, picked_up, cancelled |
dropped_off_at | TEXT | When the bike entered the shop |
promised_by | TEXT | Expected ready date |
issue_description | TEXT | What the customer reported |
drop_off_notes | TEXT | Staff notes from the drop-off conversation |
internal_notes | TEXT | Mechanic notes (not customer-visible) |
assigned_staff_id | INTEGER FK → staff | The mechanic on this ticket |
subtotal_cents | INTEGER | Calculated from lines |
discount_cents | INTEGER | NOT NULL DEFAULT 0 |
gst_cents, pst_cents | INTEGER | Calculated |
total_cents | INTEGER | Calculated |
transaction_id | INTEGER FK → transactions | Set when ticket is cashed out |
created_at, updated_at | TEXT |
Related tables
service_ticket_lines— labour and parts line itemsservice_ticket_status_history— every status transition, with timestamp and staffservice_ticket_messages— SMS / email log (incl. Twilio SIDs)
Lifecycle
See service-ticket lifecycle for the full state diagram. In short:
dropped_off → quoted (optional) → in_progress → awaiting_parts (optional) → ready → picked_up
↓ ↓ ↓ ↓
cancelled (terminal)
Each transition writes a row to service_ticket_status_history and an audit event.
Behaviors
Drop-off creation
POST /api/tickets:
- Requires
customer_id,customer_bike_id,issue_description - Generates ticket number (
T-N{0000+seq}) - Sets status to
dropped_off - Optional
assigned_staff_id(else picked up at first transition)
Line items
POST /api/tickets/{id}/lines:
- Two kinds:
labour(links to aservice_categoriesrow) orparts(links to aninventory_skusvariant) - Quantity, unit price, tax category
- Auto-recalc parent
subtotal_cents+ tax + total
PUT /api/tickets/{id}/lines/{lineId} for inline edits; DELETE for removals. Each is audit-logged.
Status transitions
POST /api/tickets/{id}/status:
- Body:
{ to: 'in_progress' | 'ready' | ... } - Validates the transition is legal (see lifecycle)
- Writes status history row
- Triggers downstream behavior on certain transitions:
ready→ optional Twilio SMS to customerpicked_up→ typically follows a sale checkoutcancelled→ may release reserved parts back to inventory
Messages
POST /api/tickets/{id}/messages:
- Logs a
service_ticket_messagesrow - If Twilio is wired and the customer is opted-in for SMS, sends via Twilio adapter
- If not wired, marked
channel='log_only' - Idempotency key per attempt (ADR-0015)
Edit
PUT /api/tickets/{id} for issue, notes, assigned staff. Inline edits on the ticket detail page in edit mode.
Delete
Blocked if the ticket has:
- Line items
- A linked transaction
- A status other than
dropped_off
Otherwise hard DELETE, audit-logged. Cancellation (not deletion) is the typical path.
Search
GET /api/tickets/search?q=...:
- Numeric query: matches
ticket_numberexactly, orcustomer.account_number - Short query (< 4 chars): structured-field matches only
- Long query: also customer name, bike model, issue description
In-situ editing surface
On the kanban screen, in edit mode:
- Column headers editable inline
- Columns reorderable via drag
- A
?badge per column opens column settings (color, default time-in-status before late warning, who can move tickets here) - Ticket card field visibility editable per card layout
On a ticket detail page, in edit mode:
- Issue and notes fields editable inline
- Line items: drag to reorder, × to remove, + to add (opens line-item composer)
- Status transition is its own UI (drag the card on kanban, or use the status dropdown on detail)
Migrated from AIM
For Swicked: 2,506 service tickets + 5,057 ticket lines + 7,340 status-history rows from sctic + related tables. Each ticket includes:
sctic.tic_desc→service_tickets.issue_descriptionsctic.tic_ptime→service_tickets.promised_by- Linked
customer_idviasctic.tic_cust - Linked
customer_bike_idvia the bike's serial number matched tosctic.tic_serno - Lines from
sctic_line→service_ticket_lines
See migration from AIM.