Order Status State Machine¶
This document describes the order status state machine implemented in orders/state_machine.py.
Overview¶
The state machine ensures orders follow valid status transitions based on their order type. This prevents invalid status changes and provides a clear workflow for each order type.
Usage¶
Basic Usage¶
from orders.state_machine import OrderStateMachine, can_transition, get_available_transitions
# Check if a transition is valid
if can_transition(order, OrderStatus.READY_TO_SHIP):
order.status = OrderStatus.READY_TO_SHIP
order.save()
# Get available next statuses for an order
available = get_available_transitions(order)
# Returns: [(OrderStatus.READY_TO_SHIP, 'Ready to Ship'), ...]
# Full transition with validation
from orders.state_machine import transition, InvalidTransitionError
try:
transition(order, OrderStatus.SHIPPED)
except InvalidTransitionError as e:
print(f"Cannot transition: {e}")
Locked States¶
Some states are "locked" and require explicit unlock before allowing changes:
# Check if order is in a locked state
if OrderStateMachine.is_locked_state(order):
# Requires force=True to change
transition(order, new_status, force=True)
# Get transitions including from locked state
available = get_available_transitions(order, include_locked=True)
Order Type Workflows¶
These can be seen in the state_machine.py code or the shared draw.io file Orders_StateMachine.drawio.
This file Uplink/docs/Orders_StateMachine.drawio.xml can also be downloaded and then uploaded to draw.io if you don't have share access to the file.
Extending the State Machine¶
Adding a New Locked State¶
- Add the status to
LOCKED_STATESinstate_machine.py:
LOCKED_STATES = {
OrderType.FORECAST_ORDER: [OrderStatus.PO_CREATED],
OrderType.SALES_ORDER: [OrderStatus.SHIPPED],
OrderType.SUPPLIER_ORDER: [OrderStatus.COMPLETED],
OrderType.STOCK_TRANSFER_ORDER: [OrderStatus.COMPLETED],
OrderType.CONVERSION_ORDER: [OrderStatus.PROCESSED],
}
- Update the template to show unlock UI when in that state.
Adding Custom Transition Rules¶
- Modify the transitions dict for the order type:
SALES_ORDER_TRANSITIONS = {
OrderStatus.DRAFT: [
OrderStatus.WAITING_FOR_PAYMENT,
OrderStatus.HELD,
OrderStatus.READY_TO_SHIP,
OrderStatus.CANCELLED,
OrderStatus.NEW_STATUS, # Add new transition
],
# ...
}
Adding a New Order Type¶
- Create the transitions dict:
NEW_ORDER_TYPE_TRANSITIONS = {
OrderStatus.DRAFT: [OrderStatus.READY_TO_PROCESS],
OrderStatus.READY_TO_PROCESS: [OrderStatus.COMPLETED],
# ...
}
- Add to
TRANSITIONS_BY_TYPE:
- Update
get_initial_status()andget_final_statuses()methods.
Integration Points¶
Views¶
The UpdateOrderStatus view in orders/views.py uses the state machine:
- Checks for locked states before allowing changes
- Validates transitions (warns if non-standard)
- Respects force_unlock parameter from frontend
The OrderDetail view uses state machine for forecast orders only:
- Shows only valid next transitions in the dropdown (not all statuses)
- Includes current status so it appears selected
- Other order types still show all valid statuses for that type
Templates¶
The order_detail.html template:
- Shows unlock switch for locked states (e.g., PO_CREATED)
- Passes force_unlock value to the backend
- Disables status select when locked
Configuration¶
Strict vs Permissive Mode¶
Currently, the state machine is permissive - it warns about non-standard transitions but allows them. To make it strict:
# In UpdateOrderStatus view, change:
if not OrderStateMachine.can_transition(order, new_status, force=force_unlock):
messages.error(request, f"Invalid transition from '{from_label}' to '{to_label}'.")
return HttpResponseRedirect(reverse("order", kwargs={"reference": order.reference}))
Audit Trail¶
Could add logging to track state changes:
def transition_with_audit(order, to_status, user, force=False):
from_status = order.status
order = transition(order, to_status, force=force)
# Log the transition
OrderStatusLog.objects.create(
order=order,
from_status=from_status,
to_status=to_status,
changed_by=user,
forced=force
)
return order
This helps track the 'journey' an order has been through and identify where issues may have occurred.