Skip to main content

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.

Drafted from planning · v0.1

Table: service_tickets

ColumnTypeNotes
idINTEGER PK
ticket_numberTEXTHuman-readable, e.g., T-N0001
customer_idINTEGER FK → customers
customer_bike_idINTEGER FK → customer_bikes
statusTEXTOne of: dropped_off, quoted, in_progress, awaiting_parts, ready, picked_up, cancelled
dropped_off_atTEXTWhen the bike entered the shop
promised_byTEXTExpected ready date
issue_descriptionTEXTWhat the customer reported
drop_off_notesTEXTStaff notes from the drop-off conversation
internal_notesTEXTMechanic notes (not customer-visible)
assigned_staff_idINTEGER FK → staffThe mechanic on this ticket
subtotal_centsINTEGERCalculated from lines
discount_centsINTEGERNOT NULL DEFAULT 0
gst_cents, pst_centsINTEGERCalculated
total_centsINTEGERCalculated
transaction_idINTEGER FK → transactionsSet when ticket is cashed out
created_at, updated_atTEXT
  • service_ticket_lines — labour and parts line items
  • service_ticket_status_history — every status transition, with timestamp and staff
  • service_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 a service_categories row) or parts (links to an inventory_skus variant)
  • 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 customer
    • picked_up → typically follows a sale checkout
    • cancelled → may release reserved parts back to inventory

Messages

POST /api/tickets/{id}/messages:

  • Logs a service_ticket_messages row
  • 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.

GET /api/tickets/search?q=...:

  • Numeric query: matches ticket_number exactly, or customer.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_descservice_tickets.issue_description
  • sctic.tic_ptimeservice_tickets.promised_by
  • Linked customer_id via sctic.tic_cust
  • Linked customer_bike_id via the bike's serial number matched to sctic.tic_serno
  • Lines from sctic_lineservice_ticket_lines

See migration from AIM.

See also