Skip to content

Picking List State Machine Documentation

This document describes the complete state machine logic for Picking List.


State Definitions

Picking List has the following 5 states:

Status CodeNameDescription
readyReadyPicking list just created, waiting to start picking
processingProcessingOperator is performing picking operations
doneDoneAll orders in the picking list have been shipped
canceledCanceledPicking list has been canceled (can be resumed)
terminatedTerminatedPicking list has been terminated (some orders canceled or other reasons)

State Transition Diagram

(State transition diagram picking_list_status.svg is currently unavailable; please refer to the "State Transition Events" table below)

D2 File: docs/state_machines/picking_list_status.d2


State Transition Events

1. start_picking (Start Picking)

Function: Start picking operations

State Transition:

  • readyprocessing

Trigger Timing:

  • Operator clicks "Start Picking" button
  • First picking item is scanned

Business Logic:

  • Record picking start time
  • Record picking operator
  • Create picking history record

Guard Conditions:

  • Picking list status must be ready
  • Picking list contains at least one picking item (has_items?)

2. cancel (Cancel Picking List)

Function: Cancel picking list (can be resumed)

State Transition:

  • readycanceled
  • processingcanceled

Trigger Timing:

  • Operator manually cancels picking list
  • Need to pause picking operations

Business Logic:

  • Record cancellation time
  • Preserve picking list data (can be resumed)

Guard Conditions:

  • Picking list status must be ready or processing

3. resume (Resume Picking List)

Function: Resume canceled picking list

State Transition:

  • canceledprocessing

Trigger Timing:

  • Operator manually resumes picking list
  • Cancellation reason has been resolved

Business Logic:

  • Clear cancellation time
  • Resume picking operations

Guard Conditions:

  • Picking list status must be canceled

4. complete (Complete Picking)

Function: Complete picking and shipment for all orders

State Transition:

  • processingdone

Trigger Timing:

  • All orders in the picking list have status changed to done
  • Automatically triggered by system (listening to order status changes)
  • Or manually marked as complete by operator

Business Logic:

  • Record completion time
  • Create completion history record
  • Release picking list resources

Guard Conditions:

  • Picking list status must be processing
  • All associated orders have status == done (all_orders_shipped?)

5. terminate (Terminate Picking List)

Function: Terminate picking list (some orders canceled or other abnormal situations)

State Transition:

  • processingterminated

Trigger Timing:

  • Operator manually terminates
  • Some orders canceled causing picking list unable to continue
  • System error requires terminating picking process

Business Logic:

  • Record termination time
  • Record termination reason
  • Unbind pending orders
  • Release allocated inventory (if applicable)

Guard Conditions:

  • Picking list status must be processing
  • Requires admin permission or special confirmation

Permission Flag Methods

The following are suggested permission methods for controlling UI button display and feature enablement:

editable?

Description: Whether picking list can be edited (modify order content)

Logic:

ruby
def editable?
  ready?
end

Returns true for states:

  • ready - Ready state allows modification of orders

pickable?

Description: Whether picking list can start picking

Logic:

ruby
def pickable?
  ready? && has_items?
end

Returns true for states:

  • ready - Ready state with picking items can start picking

completable?

Description: Whether picking list can be manually completed

Logic:

ruby
def completable?
  processing? && all_orders_shipped?
end

Returns true for states:

  • processing - Processing and all orders have been shipped

terminatable?

Description: Whether picking list can be terminated

Logic:

ruby
def terminatable?
  processing?
end

Returns true for states:

  • processing - Processing can be terminated

cancelable?

Description: Whether picking list can be canceled

Logic:

ruby
def cancelable?
  ready? || processing?
end

Returns true for states:

  • ready - Ready can be canceled
  • processing - Processing can be canceled

resumable?

Description: Whether picking list can be resumed

Logic:

ruby
def resumable?
  canceled?
end

Returns true for states:

  • canceled - Canceled can be resumed

in_progress?

Description: Whether picking list is in progress

Logic:

ruby
def in_progress?
  processing?
end

Returns true for states:

  • processing - Processing

finished?

Description: Whether picking list has finished (completed, canceled or terminated)

Logic:

ruby
def finished?
  done? || terminated? || canceled?
end

Returns true for states:

  • done - Completed
  • canceled - Canceled
  • terminated - Terminated

Business Scenarios

Scenario 1: Normal Picking Flow

  1. Create Picking List: System automatically creates picking list based on picking rules → ready
  2. Start Picking: Operator scans first item → processing
  3. Picking & Shipping: Operator completes picking, parcel, and shipment operations
  4. Auto Complete: All orders shipped → done

Scenario 2: Terminate Picking List

Case A: Partial Order Cancellation

  1. Picking list status is processing
  2. Several orders are canceled by customers
  3. Operator evaluates and decides to terminate this picking list
  4. Click "Terminate" and input reason → terminated
  5. Remaining valid orders can be reassigned to other picking lists

Case B: System Error

  1. Picking list status is processing
  2. Inventory error or system error occurs
  3. Admin decides to terminate this picking list
  4. Input termination reason → terminated
  5. Admin handles order reassignment later

Scenario 3: Partial Order Cancellation (Without Termination)

Case:

  • Picking list status is processing
  • One order is canceled by customer

Handling Logic:

  1. Remove canceled order from picking list
  2. If there are other valid orders, picking list continues normal flow
  3. If all orders are canceled, system automatically changes picking list to terminated

Automatic Trigger Checks

System should automatically check and update picking list status in the following situations:

1. After Order Status Change

Check Point: When associated order's status changes

Check Logic:

ruby
def check_completion
  return unless processing?

  if all_orders_done?
    complete!
  end
end

def check_termination
  return unless processing?

  # If all orders are canceled, automatically terminate picking list
  if orders.all?(&:canceled?)
    terminate!
  end
end

2. After Picking Item Update (Optional)

Check Point: When picking_list_item updates picked_quantity

Check Logic:

ruby
# This check is optional, depending on business requirements
def check_all_items_scanned
  return unless processing?

  if all_items_scanned?
    # Can send notification or update progress
    notify_all_items_scanned
  end
end

Database Field Description

Current timestamp fields:

Field NameTypeDescription
statusstringStatus (required)
done_atdatetimeCompletion time
warehouse_idintegerWarehouse ID
parent_idintegerParent picking list ID (if hierarchical)

UI Button Display Rules

Button display rules for each state:

StateEditStart PickingCancelResumeTerminateComplete
ready
processing✅*
done
canceled
terminated

Note:

  • ✅ indicates button should be displayed and enabled
  • ❌ indicates button should be hidden or disabled
  • ✅* indicates conditional display (processing state shows "Complete" button only when all orders are shipped)

Implementation Suggestions

Using AASM Gem

Recommend using AASM gem to implement state machine, similar to Inbound model:

ruby
class PickingList < ApplicationRecord
  include AASM

  aasm column: :status do
    state :ready, initial: true
    state :processing
    state :done
    state :terminated

    # Event: Start picking
    event :start_picking do
      transitions from: :ready, to: :processing

      after do
        update_column(:started_at, Time.current) if respond_to?(:started_at)
      end
    end

    # Event: Complete
    event :complete do
      transitions from: :processing, to: :done, guard: :all_orders_done?

      after do
        update_column(:done_at, Time.current)
      end
    end

    # Event: Terminate
    event :terminate do
      transitions from: :processing, to: :terminated

      after do
        update_column(:terminated_at, Time.current) if respond_to?(:terminated_at)
        unbind_orders
      end
    end
  end

  # Permission methods
  def editable?
    ready?
  end

  def pickable?
    ready?
  end

  def completable?
    processing? && all_orders_done?
  end

  def terminatable?
    processing?
  end

  def in_progress?
    processing?
  end

  def finished?
    done? || terminated?
  end

  # Guard methods
  def all_orders_done?
    orders.where.not(status: 'done').count.zero?
  end

  # Helper methods
  def unbind_orders
    orders.update_all(picking_list_id: nil)
  end
end

Using Enum (Simplified Version)

If you don't need complex events and callbacks, you can also use Rails built-in enum:

ruby
class PickingList < ApplicationRecord
  belongs_to :merchant
  has_many :picking_list_items, dependent: :destroy
  has_many :orders

  # Status definition
  enum :status, {
    ready: 'ready',
    processing: 'processing',
    done: 'done',
    terminated: 'terminated'
  }

  validates :status, inclusion: { in: statuses.keys }

  # Permission methods
  def editable?
    ready?
  end

  def pickable?
    ready?
  end

  def completable?
    processing? && all_orders_done?
  end

  def terminatable?
    processing?
  end

  def in_progress?
    processing?
  end

  def finished?
    done? || terminated?
  end

  # State transition methods
  def start_picking!
    raise AppErrors::Error::AppError.new(AppErrors::Error::INVALID_STATUS) unless pickable?
    update!(status: :processing, started_at: Time.current)
  end

  def complete!
    raise AppErrors::Error::AppError.new(AppErrors::Error::INVALID_STATUS) unless completable?
    update!(status: :done, done_at: Time.current)
  end

  def terminate!(reason = nil)
    raise AppErrors::Error::AppError.new(AppErrors::Error::INVALID_STATUS) unless terminatable?
    update!(
      status: :terminated,
      terminated_at: Time.current,
      terminate_reason: reason
    )
    unbind_orders
  end

  # Helper methods
  def all_orders_done?
    orders.where.not(status: 'done').count.zero?
  end

  def unbind_orders
    orders.update_all(picking_list_id: nil)
  end

  # Auto-check methods (called when order status changes)
  def check_completion
    return unless processing?

    if all_orders_done?
      complete!
    end
  end

  def check_termination
    return unless processing?

    if orders.all?(&:canceled?)
      terminate!('All orders canceled')
    end
  end
end

  • Order State Machine - Order state machine documentation
  • Inbound State Machine - Inbound state machine documentation
  • Picking List Operations - How operators use picking lists